remocon 0.3.1 → 0.4.0.pre.1
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 +4 -4
- data/.rubocop.yml +1 -1
- data/Gemfile.lock +1 -1
- data/lib/remocon.rb +1 -0
- data/lib/remocon/cli.rb +1 -0
- data/lib/remocon/command/lib/config.rb +4 -0
- data/lib/remocon/command/lib/request.rb +62 -0
- data/lib/remocon/command/pull_command.rb +114 -15
- data/lib/remocon/command/push_command.rb +18 -68
- data/lib/remocon/command/validate_command.rb +22 -5
- data/lib/remocon/sorter/condition_sorter.rb +1 -0
- data/lib/remocon/sorter/parameter_sorter.rb +1 -1
- data/lib/remocon/version.rb +1 -1
- data/sample/basketball-b8548/config.json +3 -11
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb536a401c5e10685550492b77a5cda9ac02a001
|
4
|
+
data.tar.gz: 51f618bb2ea5efc99a418a057ac90b1bc5965cca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eb31b664990aa124faece75e0d8cd4d0f789ce3c176e0b62b4348c7a69bb8b88a0854f1bebd6afc51744f00103995d6e2b7a4d249a7d382461cf2df79117be80
|
7
|
+
data.tar.gz: ad58cfeee9e91c53f8a2a21927298d384a662f520de1f607b9d94b4b2561c0954904b6cbbfb3a2a7ec034b0db91e773e93361da42468a61e8055cfe601de29fb
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
data/lib/remocon.rb
CHANGED
@@ -40,6 +40,7 @@ require "remocon/normalizer/type_normalizer_factory"
|
|
40
40
|
|
41
41
|
require "remocon/command/lib/config"
|
42
42
|
require "remocon/command/lib/interpreter_helper"
|
43
|
+
require "remocon/command/lib/request"
|
43
44
|
|
44
45
|
require "remocon/command/create_command"
|
45
46
|
require "remocon/command/get_token_command"
|
data/lib/remocon/cli.rb
CHANGED
@@ -33,6 +33,7 @@ module Remocon
|
|
33
33
|
end
|
34
34
|
|
35
35
|
desc "pull", "Pull remote configs"
|
36
|
+
option :merge, type: :boolean, default: true, desc: "use the hash merge algorithm if true. default is true."
|
36
37
|
option :prefix, type: :string, desc: "the directory name which will contain project-related files"
|
37
38
|
option :token, type: :string, desc: "access token to your project"
|
38
39
|
option :id, type: :string, desc: "your project id"
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remocon
|
4
|
+
module Request
|
5
|
+
def self.push(config)
|
6
|
+
raise "etag should be specified. If you want to ignore this error, then please add --force option" unless config.etag
|
7
|
+
|
8
|
+
client, uri = Request.build_client(config)
|
9
|
+
|
10
|
+
headers = {
|
11
|
+
"Authorization" => "Bearer #{config.token}",
|
12
|
+
"Content-Type" => "application/json; UTF8",
|
13
|
+
"If-Match" => config.etag,
|
14
|
+
}
|
15
|
+
|
16
|
+
request = Net::HTTP::Put.new(uri.request_uri, headers)
|
17
|
+
request.body = +""
|
18
|
+
request.body << File.read(config.config_json_file_path).delete("\r\n")
|
19
|
+
|
20
|
+
response = client.request(request)
|
21
|
+
|
22
|
+
response_body = begin
|
23
|
+
json_str = response.try(:read_body)
|
24
|
+
(json_str ? JSON.parse(json_str) : {}).with_indifferent_access
|
25
|
+
end
|
26
|
+
|
27
|
+
return response, response_body
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.pull(config)
|
31
|
+
raw_json, etag = open(config.endpoint, "Authorization" => "Bearer #{config.token}") do |io|
|
32
|
+
[io.read, io.meta["etag"]]
|
33
|
+
end
|
34
|
+
|
35
|
+
[raw_json, etag]
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.fetch_etag(config)
|
39
|
+
# remote config api doesn't support head request so we need to use GET instead.
|
40
|
+
|
41
|
+
client, uri = Request.build_client(config)
|
42
|
+
|
43
|
+
headers = {
|
44
|
+
"Authorization" => "Bearer #{config.token}",
|
45
|
+
"Content-Type" => "application/json; UTF8",
|
46
|
+
"Content-Encoding" => "gzip",
|
47
|
+
}
|
48
|
+
|
49
|
+
request = Net::HTTP::Get.new(uri.request_uri, headers)
|
50
|
+
response = client.request(request)
|
51
|
+
|
52
|
+
response.kind_of?(Net::HTTPOK) && response.header["etag"]
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.build_client(config)
|
56
|
+
uri = URI.parse(config.endpoint)
|
57
|
+
client = Net::HTTP.new(uri.host, uri.port)
|
58
|
+
client.use_ssl = true
|
59
|
+
return client, uri
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -3,13 +3,40 @@
|
|
3
3
|
module Remocon
|
4
4
|
module Command
|
5
5
|
class Pull
|
6
|
+
class RemoteConfig
|
7
|
+
include Remocon::InterpreterHelper
|
8
|
+
|
9
|
+
attr_reader :config
|
10
|
+
|
11
|
+
def initialize(opts)
|
12
|
+
@config = Remocon::Config.new(opts)
|
13
|
+
end
|
14
|
+
|
15
|
+
def require_parameters_file_path
|
16
|
+
config.parameters_file_path
|
17
|
+
end
|
18
|
+
|
19
|
+
def require_conditions_file_path
|
20
|
+
config.conditions_file_path
|
21
|
+
end
|
22
|
+
|
23
|
+
def raw_conditions
|
24
|
+
YAML.safe_load(File.open(require_conditions_file_path).read).map(&:with_indifferent_access)
|
25
|
+
end
|
26
|
+
|
27
|
+
def raw_parameters
|
28
|
+
YAML.safe_load(File.open(require_parameters_file_path).read).with_indifferent_access
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
6
32
|
include Remocon::InterpreterHelper
|
7
33
|
|
8
|
-
attr_reader :config, :cmd_opts
|
34
|
+
attr_reader :config, :cmd_opts, :left
|
9
35
|
|
10
36
|
def initialize(opts)
|
11
37
|
@config = Remocon::Config.new(opts)
|
12
38
|
@cmd_opts = { validate_only: false }
|
39
|
+
@left = RemoteConfig.new(opts)
|
13
40
|
end
|
14
41
|
|
15
42
|
def require_parameters_file_path
|
@@ -21,7 +48,7 @@ module Remocon
|
|
21
48
|
end
|
22
49
|
|
23
50
|
def run
|
24
|
-
raw_json, etag =
|
51
|
+
raw_json, etag = Remocon::Request.pull(config)
|
25
52
|
|
26
53
|
raw_hash = JSON.parse(raw_json).with_indifferent_access
|
27
54
|
|
@@ -30,18 +57,100 @@ module Remocon
|
|
30
57
|
conditions = raw_hash[:conditions] || []
|
31
58
|
parameters = raw_hash[:parameters] || {}
|
32
59
|
|
60
|
+
if config.merge? && File.exist?(config.parameters_file_path) && File.exist?(config.parameters_file_path)
|
61
|
+
unchanged_conditions, added_conditions, changed_conditions, = conditions_diff(left.raw_conditions, conditions)
|
62
|
+
unchanged_parameters, added_parameters, changed_parameters, = parameters_diff(left.raw_parameters, parameters)
|
63
|
+
|
64
|
+
conditions_yaml = JSON.parse(sort_conditions(unchanged_conditions + added_conditions + changed_conditions).to_json).to_yaml
|
65
|
+
parameters_yaml = JSON.parse(sort_parameters(unchanged_parameters.merge(added_parameters).merge(changed_parameters)).to_json).to_yaml
|
66
|
+
else
|
67
|
+
conditions_yaml = JSON.parse(Remocon::ConditionFileDumper.new(sort_conditions(conditions)).dump.to_json).to_yaml
|
68
|
+
parameters_yaml = JSON.parse(Remocon::ParameterFileDumper.new(sort_parameters(parameters)).dump.to_json).to_yaml
|
69
|
+
end
|
70
|
+
|
71
|
+
write_to_files(conditions_yaml, parameters_yaml, etag)
|
72
|
+
end
|
73
|
+
|
74
|
+
def conditions_diff(left, right)
|
75
|
+
left_names = left.map { |c| c[:name] }
|
76
|
+
right_names = right.map { |c| c[:name] }
|
77
|
+
|
78
|
+
added_names = right_names - left_names
|
79
|
+
removed_names = left_names - right_names
|
80
|
+
|
81
|
+
added = added_names.each_with_object([]) do |k, acc|
|
82
|
+
acc.push(right.find { |c| c[:name] == k })
|
83
|
+
end
|
84
|
+
|
85
|
+
changed = []
|
86
|
+
unchanged = []
|
87
|
+
|
88
|
+
(right_names & left_names).each do |k|
|
89
|
+
old = left.find { |c| c[:name] == k }
|
90
|
+
new = Remocon::ConditionFileDumper.new(right.find { |c| c[:name] == k }).dump
|
91
|
+
|
92
|
+
if old == new
|
93
|
+
unchanged.push(old)
|
94
|
+
else
|
95
|
+
changed.push(new)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
removed = []
|
100
|
+
|
101
|
+
removed_names.each do |k|
|
102
|
+
removed.push(left.find { |c| c[:name] == k })
|
103
|
+
end
|
104
|
+
|
105
|
+
[unchanged, added, changed, removed]
|
106
|
+
end
|
107
|
+
|
108
|
+
def parameters_diff(left, right)
|
109
|
+
added_keys = right.keys - left.keys
|
110
|
+
removed_keys = left.keys - right.keys
|
111
|
+
|
112
|
+
added = added_keys.each_with_object({}) do |k, acc|
|
113
|
+
acc.merge!(Remocon::ParameterFileDumper.new({ k => right[k] }).dump)
|
114
|
+
end
|
115
|
+
|
116
|
+
changed = {}
|
117
|
+
unchanged = {}
|
118
|
+
|
119
|
+
(right.keys & left.keys).each do |k|
|
120
|
+
old = { k => left[k] }
|
121
|
+
new = Remocon::ParameterFileDumper.new({ k => right[k] }).dump
|
122
|
+
|
123
|
+
if old == new
|
124
|
+
unchanged.merge!(old)
|
125
|
+
else
|
126
|
+
changed.merge!(new)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
removed = {}
|
131
|
+
|
132
|
+
removed_keys.each do |k|
|
133
|
+
removed[k] = left[k]
|
134
|
+
end
|
135
|
+
|
136
|
+
[unchanged, added, changed, removed]
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def write_to_files(conditions_yaml, parameters_yaml, etag)
|
33
142
|
File.open(config.conditions_file_path, "w+") do |f|
|
34
|
-
f.write(
|
143
|
+
f.write(conditions_yaml)
|
35
144
|
f.flush
|
36
145
|
end
|
37
146
|
|
38
147
|
File.open(config.parameters_file_path, "w+") do |f|
|
39
|
-
f.write(
|
148
|
+
f.write(parameters_yaml)
|
40
149
|
f.flush
|
41
150
|
end
|
42
151
|
|
43
152
|
File.open(config.config_json_file_path, "w+") do |f|
|
44
|
-
f.write(JSON.pretty_generate({ conditions:
|
153
|
+
f.write(JSON.pretty_generate({ conditions: condition_array, parameters: parameter_hash }))
|
45
154
|
f.flush
|
46
155
|
end
|
47
156
|
|
@@ -50,16 +159,6 @@ module Remocon
|
|
50
159
|
f.flush
|
51
160
|
end
|
52
161
|
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def do_request
|
57
|
-
raw_json, etag = open(config.endpoint, "Authorization" => "Bearer #{config.token}") do |io|
|
58
|
-
[io.read, io.meta["etag"]]
|
59
|
-
end
|
60
|
-
|
61
|
-
[raw_json, etag]
|
62
|
-
end
|
63
162
|
end
|
64
163
|
end
|
65
164
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# frozen_string_literal:
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Remocon
|
4
4
|
module Command
|
@@ -11,72 +11,23 @@ module Remocon
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def run
|
14
|
-
# to prevent a real request in spec
|
15
14
|
do_request
|
16
15
|
end
|
17
16
|
|
18
|
-
def client
|
19
|
-
return @client if @client
|
20
|
-
|
21
|
-
client = Net::HTTP.new(uri.host, uri.port)
|
22
|
-
client.use_ssl = true
|
23
|
-
|
24
|
-
@client = client
|
25
|
-
end
|
26
|
-
|
27
|
-
def request
|
28
|
-
return @request if @request
|
29
|
-
|
30
|
-
raise "etag should be specified. If you want to ignore this error, then add --force option" unless config.etag
|
31
|
-
|
32
|
-
headers = {
|
33
|
-
"Authorization" => "Bearer #{config.token}",
|
34
|
-
"Content-Type" => "application/json; UTF8",
|
35
|
-
"If-Match" => config.etag,
|
36
|
-
}
|
37
|
-
|
38
|
-
request = Net::HTTP::Put.new(uri.request_uri, headers)
|
39
|
-
request.body = ""
|
40
|
-
request.body << File.read(config.config_json_file_path).delete("\r\n")
|
41
|
-
|
42
|
-
@request = request
|
43
|
-
end
|
44
|
-
|
45
17
|
private
|
46
18
|
|
47
|
-
def uri
|
48
|
-
@uri ||= URI.parse(config.endpoint)
|
49
|
-
end
|
50
|
-
|
51
19
|
def do_request
|
52
|
-
response =
|
20
|
+
response, response_body = Remocon::Request.push(config)
|
53
21
|
|
54
|
-
response_body
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
# intentional behavior
|
63
|
-
STDERR.puts "Updated successfully."
|
64
|
-
when Net::HTTPBadRequest
|
65
|
-
# sent json contains errors
|
66
|
-
parse_error_body(response, response_body) if response_body
|
67
|
-
STDERR.puts "400 but no error body" unless response_body
|
68
|
-
when Net::HTTPUnauthorized
|
69
|
-
# token was expired
|
70
|
-
STDERR.puts "401 Unauthorized. A token might be expired or invalid."
|
71
|
-
when Net::HTTPForbidden
|
72
|
-
# remote config api might be disabled or not yet activated
|
73
|
-
STDERR.puts "403 Forbidden. RemoteConfig API might not be activated or be disabled."
|
74
|
-
when Net::HTTPConflict
|
75
|
-
# local content is out-to-date
|
76
|
-
STDERR.puts "409 Conflict. Remote was updated. Please update your local files"
|
22
|
+
(response.kind_of?(Net::HTTPOK) && parse_success_body(response, response_body)).tap do |result|
|
23
|
+
unless result
|
24
|
+
if response_body.blank?
|
25
|
+
STDERR.puts "No error body"
|
26
|
+
else
|
27
|
+
parse_error_body(response, response_body)
|
28
|
+
end
|
29
|
+
end
|
77
30
|
end
|
78
|
-
|
79
|
-
response.kind_of?(Net::HTTPOK)
|
80
31
|
end
|
81
32
|
|
82
33
|
def parse_success_body(response, _success_body)
|
@@ -84,19 +35,18 @@ module Remocon
|
|
84
35
|
|
85
36
|
return unless etag
|
86
37
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
f.flush
|
91
|
-
end
|
92
|
-
else
|
93
|
-
STDOUT.puts etag
|
38
|
+
File.open(config.etag_file_path, "w+") do |f|
|
39
|
+
f.write(etag)
|
40
|
+
f.flush
|
94
41
|
end
|
42
|
+
|
43
|
+
STDOUT.puts(etag)
|
44
|
+
true
|
95
45
|
end
|
96
46
|
|
97
47
|
def parse_error_body(_response, error_body)
|
98
|
-
STDERR.puts
|
99
|
-
STDERR.puts
|
48
|
+
STDERR.puts error_body[:error][:status]
|
49
|
+
STDERR.puts error_body[:error][:message]
|
100
50
|
|
101
51
|
error_body.dig(:error, :details)&.each do |k|
|
102
52
|
# for now, see only errors below
|
@@ -8,7 +8,7 @@ module Remocon
|
|
8
8
|
attr_reader :config, :cmd_opts
|
9
9
|
|
10
10
|
def initialize(opts)
|
11
|
-
@config = Remocon::Config.new(opts)
|
11
|
+
@config = Remocon::Config.new(opts.merge(force: false))
|
12
12
|
@cmd_opts = { validate_only: true }
|
13
13
|
end
|
14
14
|
|
@@ -23,18 +23,22 @@ module Remocon
|
|
23
23
|
def run
|
24
24
|
validate_options
|
25
25
|
|
26
|
-
errors = parameter_errors + condition_errors
|
26
|
+
errors = parameter_errors + condition_errors + etag_errors
|
27
27
|
|
28
|
+
print_errors(errors)
|
29
|
+
|
30
|
+
errors.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
def print_errors(errors)
|
28
34
|
if errors.empty?
|
29
35
|
STDOUT.puts "No error was found."
|
30
36
|
else
|
31
37
|
errors.each do |e|
|
32
38
|
STDERR.puts "#{e.class} #{e.message}"
|
33
|
-
STDERR.puts e.backtrace
|
39
|
+
STDERR.puts e.backtrace&.join("\n")
|
34
40
|
end
|
35
41
|
end
|
36
|
-
|
37
|
-
errors.empty?
|
38
42
|
end
|
39
43
|
|
40
44
|
private
|
@@ -42,6 +46,19 @@ module Remocon
|
|
42
46
|
def validate_options
|
43
47
|
raise ValidationError, "A condition file must exist" unless File.exist?(config.conditions_file_path)
|
44
48
|
raise ValidationError, "A parameter file must exist" unless File.exist?(config.parameters_file_path)
|
49
|
+
raise ValidationError, "An etag file must exist" unless File.exist?(config.etag_file_path)
|
50
|
+
end
|
51
|
+
|
52
|
+
def remote_etag
|
53
|
+
@remote_etag ||= Remocon::Request.fetch_etag(config)
|
54
|
+
end
|
55
|
+
|
56
|
+
def etag_errors
|
57
|
+
if config.etag != remote_etag
|
58
|
+
[ValidationError.new("#{config.etag} is found but the latest etag is #{remote_etag || 'none'}")]
|
59
|
+
else
|
60
|
+
[]
|
61
|
+
end
|
45
62
|
end
|
46
63
|
end
|
47
64
|
end
|
@@ -6,7 +6,7 @@ module Remocon
|
|
6
6
|
|
7
7
|
def sort_parameters(parameters)
|
8
8
|
arr = parameters.sort.map do |k, v|
|
9
|
-
hash_arr = v.sort { |(a, _), (b, _)| PARAMETER_KEYS.index(a) <=> PARAMETER_KEYS.index(b) }
|
9
|
+
hash_arr = v.symbolize_keys.sort { |(a, _), (b, _)| PARAMETER_KEYS.index(a) <=> PARAMETER_KEYS.index(b) }
|
10
10
|
.map do |k1, v1|
|
11
11
|
{
|
12
12
|
k1 => k1.to_sym == :conditions ? sort_parameters(v1) : v1
|
data/lib/remocon/version.rb
CHANGED
@@ -14,19 +14,11 @@
|
|
14
14
|
"parameters": {
|
15
15
|
"key1": {
|
16
16
|
"defaultValue": {
|
17
|
-
"value": "100"
|
18
|
-
"conditions": {
|
19
|
-
"condition1": {
|
20
|
-
"value": "200"
|
21
|
-
},
|
22
|
-
"zxczx": {
|
23
|
-
"value": "100"
|
24
|
-
}
|
25
|
-
}
|
17
|
+
"value": "100"
|
26
18
|
},
|
27
19
|
"conditionalValues": {
|
28
20
|
"condition1": {
|
29
|
-
"value": "
|
21
|
+
"value": "400"
|
30
22
|
},
|
31
23
|
"zxczx": {
|
32
24
|
"value": "100"
|
@@ -55,7 +47,7 @@
|
|
55
47
|
},
|
56
48
|
"key6": {
|
57
49
|
"defaultValue": {
|
58
|
-
"value": "{\"value\":\"
|
50
|
+
"value": "{\"value\":\"234234234234234\"}"
|
59
51
|
}
|
60
52
|
},
|
61
53
|
"key7": {
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: remocon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0.pre.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jumpei Matsuda
|
8
8
|
autorequire:
|
9
9
|
bindir: cmd
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-08-
|
11
|
+
date: 2018-08-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -153,6 +153,7 @@ files:
|
|
153
153
|
- lib/remocon/command/get_token_command.rb
|
154
154
|
- lib/remocon/command/lib/config.rb
|
155
155
|
- lib/remocon/command/lib/interpreter_helper.rb
|
156
|
+
- lib/remocon/command/lib/request.rb
|
156
157
|
- lib/remocon/command/pull_command.rb
|
157
158
|
- lib/remocon/command/push_command.rb
|
158
159
|
- lib/remocon/command/validate_command.rb
|
@@ -197,9 +198,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
197
198
|
version: '0'
|
198
199
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
199
200
|
requirements:
|
200
|
-
- - "
|
201
|
+
- - ">"
|
201
202
|
- !ruby/object:Gem::Version
|
202
|
-
version:
|
203
|
+
version: 1.3.1
|
203
204
|
requirements: []
|
204
205
|
rubyforge_project:
|
205
206
|
rubygems_version: 2.5.2.3
|