remocon 0.1.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/.envrc +1 -0
- data/.gitignore +313 -0
- data/.rspec +3 -0
- data/.rubocop.yml +70 -0
- data/.ruby-version +1 -0
- data/.travis.yml +13 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +72 -0
- data/LICENSE.txt +21 -0
- data/README.md +91 -0
- data/Rakefile +6 -0
- data/bin/console +15 -0
- data/bin/get_access_token +11 -0
- data/bin/get_access_token.py +18 -0
- data/bin/put_by_curl +13 -0
- data/bin/setup +8 -0
- data/cmd/remocon +6 -0
- data/lib/remocon.rb +47 -0
- data/lib/remocon/cli.rb +41 -0
- data/lib/remocon/command/create_command.rb +48 -0
- data/lib/remocon/command/lib/interpreter_helper.rb +44 -0
- data/lib/remocon/command/pull_command.rb +66 -0
- data/lib/remocon/command/push_command.rb +119 -0
- data/lib/remocon/command/validate_command.rb +38 -0
- data/lib/remocon/dumper/condition_file_dumper.rb +14 -0
- data/lib/remocon/dumper/parameter_file_dumper.rb +22 -0
- data/lib/remocon/error/unsupported_type_error.rb +9 -0
- data/lib/remocon/error/validation_error.rb +10 -0
- data/lib/remocon/interpreter/condition_file_interpreter.rb +32 -0
- data/lib/remocon/interpreter/parameter_file_interpreter.rb +60 -0
- data/lib/remocon/normalizer/boolean_normalizer.rb +23 -0
- data/lib/remocon/normalizer/integer_normalizer.rb +23 -0
- data/lib/remocon/normalizer/json_normalizer.rb +20 -0
- data/lib/remocon/normalizer/normalizer.rb +33 -0
- data/lib/remocon/normalizer/string_normalizer.rb +13 -0
- data/lib/remocon/normalizer/type_normalizer_factory.rb +25 -0
- data/lib/remocon/normalizer/void_normalizer.rb +9 -0
- data/lib/remocon/sorter/condition_sorter.rb +23 -0
- data/lib/remocon/sorter/parameter_sorter.rb +24 -0
- data/lib/remocon/type.rb +11 -0
- data/lib/remocon/util/array.rb +15 -0
- data/lib/remocon/util/hash.rb +29 -0
- data/lib/remocon/util/string.rb +13 -0
- data/lib/remocon/version.rb +5 -0
- data/remocon.gemspec +42 -0
- data/sample/basketball-b8548/conditions.yml +7 -0
- data/sample/basketball-b8548/config.json +67 -0
- data/sample/basketball-b8548/etag +1 -0
- data/sample/basketball-b8548/parameters.yml +20 -0
- metadata +193 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Jumpei Matsuda
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# Remocon
|
2
|
+
|
3
|
+
*remocon* is a CLI for Firebase Remote Config via its REST API.
|
4
|
+
Conditions and parameters are managed by YAML files.
|
5
|
+
|
6
|
+
*This is still in beta. A diff mode should be supported when managing configs heavily.*
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
|
10
|
+
You need to get an access token for your firebase project.
|
11
|
+
|
12
|
+
```bash
|
13
|
+
export FIREBASE_PROJECT_ID='your project id'
|
14
|
+
export REMOTE_CONFIG_ACCESS_TOKEN='your access token'
|
15
|
+
```
|
16
|
+
|
17
|
+
### Get the current configs into your local
|
18
|
+
|
19
|
+
```bash
|
20
|
+
bundle exec remocon pull --dest=${path to dir}
|
21
|
+
|
22
|
+
# you can see ${path to dir}/${FIREBASE_PROJECT_ID}/{paremeters.yml, conditions.yml, config.json, etag}
|
23
|
+
```
|
24
|
+
|
25
|
+
### Edit configs on your local
|
26
|
+
|
27
|
+
Condition definitions and parameter definitions are separated. You should modify these files.
|
28
|
+
|
29
|
+
*parameters.yml*
|
30
|
+
|
31
|
+
```yaml
|
32
|
+
key1: # key name
|
33
|
+
value: 100 # default value
|
34
|
+
conditions:
|
35
|
+
condition1:
|
36
|
+
value: 200 # a value to be used if condition1 is satisfied
|
37
|
+
condition2:
|
38
|
+
file: path_to_file # you can use file content. the file content is used for a value
|
39
|
+
```
|
40
|
+
|
41
|
+
*conditions.yml*
|
42
|
+
|
43
|
+
```yaml
|
44
|
+
- name: condition1 # condition name
|
45
|
+
expression: device.os == 'android' # expression
|
46
|
+
tagColor: "INDIGO" # color name
|
47
|
+
- name: condition2
|
48
|
+
expression: device.os == 'ios'
|
49
|
+
tagColor: CYAN
|
50
|
+
```
|
51
|
+
|
52
|
+
### Update configs on remote
|
53
|
+
|
54
|
+
```bash
|
55
|
+
bundle exec remocon push --source=${path to a json file} --etag=${string or path to a file}
|
56
|
+
```
|
57
|
+
|
58
|
+
## Installation
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
gem 'remocon'
|
62
|
+
```
|
63
|
+
|
64
|
+
## Format
|
65
|
+
|
66
|
+
### Parameters
|
67
|
+
|
68
|
+
You can use String, Boolean, Integer, Json validators like below.
|
69
|
+
|
70
|
+
```yaml
|
71
|
+
key:
|
72
|
+
value: # optional (either of this or file is required). Raw value and hash are allowed.
|
73
|
+
file: # optional (either of this or value is required). File content value.
|
74
|
+
normalizer: # optional. Either of ["integer", "string", "boolean", "json", "void"] (default: void).
|
75
|
+
conditions: # optional. If you want use conditional values, then you need to create this section.
|
76
|
+
condition_name: # must be in condition definitions.
|
77
|
+
value: ...
|
78
|
+
file: ...
|
79
|
+
```
|
80
|
+
|
81
|
+
### Conditions
|
82
|
+
|
83
|
+
It seems only three fields are supported by Remote Config. They are name, expression and tagColor.
|
84
|
+
|
85
|
+
## Contributing
|
86
|
+
|
87
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/jmatsu/remocon .
|
88
|
+
|
89
|
+
## License
|
90
|
+
|
91
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'remocon'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
# python 2.x
|
4
|
+
|
5
|
+
import sys
|
6
|
+
from oauth2client.service_account import ServiceAccountCredentials
|
7
|
+
|
8
|
+
def print_access_token():
|
9
|
+
argv = sys.argv
|
10
|
+
if (len(argv) != 2):
|
11
|
+
print >> sys.stderr, 'a filepath to service-account.json must be specified as the 1st argument.'
|
12
|
+
quit()
|
13
|
+
credentials = ServiceAccountCredentials.from_json_keyfile_name(
|
14
|
+
argv[1], ['https://www.googleapis.com/auth/firebase.remoteconfig'])
|
15
|
+
access_token_info = credentials.get_access_token()
|
16
|
+
print(access_token_info.access_token)
|
17
|
+
|
18
|
+
print_access_token
|
data/bin/put_by_curl
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
set -eu
|
4
|
+
|
5
|
+
etag_file="$1"
|
6
|
+
config_json_file="$2"
|
7
|
+
|
8
|
+
curl --compressed \
|
9
|
+
-H "Content-Type: application/json; UTF8" \
|
10
|
+
-H "If-Match: $(cat $etag_file)" \
|
11
|
+
-H "Authorization: Bearer ${REMOTE_CONFIG_ACCESS_TOKEN}" \
|
12
|
+
-X PUT "https://firebaseremoteconfig.googleapis.com/v1/projects/${FIREBASE_PROJECT_ID}/remoteConfig" \
|
13
|
+
-d "@$config_json_file"
|
data/bin/setup
ADDED
data/cmd/remocon
ADDED
data/lib/remocon.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'json'
|
5
|
+
require 'thor'
|
6
|
+
require 'active_support'
|
7
|
+
require 'active_support/core_ext'
|
8
|
+
require 'singleton'
|
9
|
+
require 'open-uri'
|
10
|
+
require 'fileutils'
|
11
|
+
require 'net/http'
|
12
|
+
|
13
|
+
require 'remocon/util/array'
|
14
|
+
require 'remocon/util/hash'
|
15
|
+
require 'remocon/util/string'
|
16
|
+
|
17
|
+
require 'remocon/version'
|
18
|
+
require 'remocon/type'
|
19
|
+
|
20
|
+
require 'remocon/error/unsupported_type_error'
|
21
|
+
require 'remocon/error/validation_error'
|
22
|
+
|
23
|
+
require 'remocon/sorter/condition_sorter'
|
24
|
+
require 'remocon/sorter/parameter_sorter'
|
25
|
+
|
26
|
+
require 'remocon/dumper/condition_file_dumper'
|
27
|
+
require 'remocon/dumper/parameter_file_dumper'
|
28
|
+
|
29
|
+
require 'remocon/interpreter/condition_file_interpreter'
|
30
|
+
require 'remocon/interpreter/parameter_file_interpreter'
|
31
|
+
|
32
|
+
require 'remocon/normalizer/normalizer'
|
33
|
+
require 'remocon/normalizer/boolean_normalizer'
|
34
|
+
require 'remocon/normalizer/integer_normalizer'
|
35
|
+
require 'remocon/normalizer/json_normalizer'
|
36
|
+
require 'remocon/normalizer/string_normalizer'
|
37
|
+
require 'remocon/normalizer/void_normalizer'
|
38
|
+
require 'remocon/normalizer/type_normalizer_factory'
|
39
|
+
|
40
|
+
require 'remocon/command/lib/interpreter_helper'
|
41
|
+
|
42
|
+
require 'remocon/command/create_command'
|
43
|
+
require 'remocon/command/pull_command'
|
44
|
+
require 'remocon/command/push_command'
|
45
|
+
require 'remocon/command/validate_command'
|
46
|
+
|
47
|
+
require 'remocon/cli'
|
data/lib/remocon/cli.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remocon
|
4
|
+
class CLI < ::Thor
|
5
|
+
desc 'create', 'Create a json to be pushed'
|
6
|
+
option :parameters, type: :string, required: true, desc: 'a filepath of parameters file'
|
7
|
+
option :conditions, type: :string, required: true, desc: 'a filepath of conditions file'
|
8
|
+
option :dest, type: :string, desc: 'a filepath or directory path of destination'
|
9
|
+
def create
|
10
|
+
execute(Remocon::Command::Create)
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'push', 'Upload remote configs based on a source json file'
|
14
|
+
class_option :source, type: :string, desc: 'a filepath of a source json file'
|
15
|
+
option :etag, type: :string, desc: 'a filepath or raw value of etag'
|
16
|
+
option :dest, type: :string, desc: 'a filepath or directory path of destination'
|
17
|
+
option :force, type: :boolean, default: false, desc: 'force to ignore some warnings'
|
18
|
+
def push
|
19
|
+
execute(Remocon::Command::Push)
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'pull', 'Pull remote configs'
|
23
|
+
option :dest, type: :string, desc: 'a filepath or directory path of destination'
|
24
|
+
def pull
|
25
|
+
execute(Remocon::Command::Pull)
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'validate', 'Validate yml files'
|
29
|
+
option :parameters, type: :string, required: true, desc: 'a filepath of parameters file'
|
30
|
+
option :conditions, type: :string, required: true, desc: 'a filepath of conditions file'
|
31
|
+
def validate
|
32
|
+
execute(Remocon::Command::Validate)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def execute(klass)
|
38
|
+
klass.new(options).run
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remocon
|
4
|
+
module Command
|
5
|
+
class Create
|
6
|
+
include Remocon::InterpreterHelper
|
7
|
+
|
8
|
+
def initialize(opts)
|
9
|
+
@opts = opts
|
10
|
+
|
11
|
+
@project_id = ENV.fetch('FIREBASE_PROJECT_ID')
|
12
|
+
@conditions_filepath = @opts[:conditions]
|
13
|
+
@parameters_filepath = @opts[:parameters]
|
14
|
+
@dest_dir = File.join(@opts[:dest], @project_id) if @opts[:dest]
|
15
|
+
|
16
|
+
@cmd_opts = { validate_only: false }
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
validate_options
|
21
|
+
|
22
|
+
artifact = {
|
23
|
+
conditions: condition_array,
|
24
|
+
parameters: parameter_hash
|
25
|
+
}.skip_nil_values.stringify_values
|
26
|
+
|
27
|
+
if @dest_dir
|
28
|
+
File.open(File.join(@dest_dir, 'config.json'), 'w+') do |f|
|
29
|
+
# remote config allows only string values ;(
|
30
|
+
f.write(JSON.pretty_generate(artifact))
|
31
|
+
f.flush
|
32
|
+
end
|
33
|
+
else
|
34
|
+
STDOUT.puts JSON.pretty_generate(artifact)
|
35
|
+
end
|
36
|
+
|
37
|
+
artifact
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def validate_options
|
43
|
+
raise ValidationError, 'A condition file must exist' unless File.exist?(@conditions_filepath)
|
44
|
+
raise ValidationError, 'A parameter file must exist' unless File.exist?(@parameters_filepath)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remocon
|
4
|
+
module InterpreterHelper
|
5
|
+
include Remocon::ConditionSorter
|
6
|
+
include Remocon::ParameterSorter
|
7
|
+
|
8
|
+
attr_reader :parameters_filepath, :conditions_filepath, :cmd_opts
|
9
|
+
|
10
|
+
def read_parameters
|
11
|
+
@read_parameters ||= begin
|
12
|
+
parameter_interpreter = Remocon::ParameterFileInterpreter.new(parameters_filepath)
|
13
|
+
parameter_interpreter.read(condition_names, cmd_opts)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def parameter_hash
|
18
|
+
@parameter_hash ||= sort_parameters(read_parameters.first)
|
19
|
+
end
|
20
|
+
|
21
|
+
def parameter_errors
|
22
|
+
@parameter_errors ||= read_parameters.second
|
23
|
+
end
|
24
|
+
|
25
|
+
def read_conditions
|
26
|
+
@read_conditions ||= begin
|
27
|
+
condition_interpreter = Remocon::ConditionFileInterpreter.new(conditions_filepath)
|
28
|
+
condition_interpreter.read(cmd_opts)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def condition_array
|
33
|
+
@condition_array ||= sort_conditions(read_conditions.first)
|
34
|
+
end
|
35
|
+
|
36
|
+
def condition_errors
|
37
|
+
@condition_errors ||= read_conditions.second
|
38
|
+
end
|
39
|
+
|
40
|
+
def condition_names
|
41
|
+
@condition_names ||= condition_array.map { |e| e[:name] }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Remocon
|
4
|
+
module Command
|
5
|
+
class Pull
|
6
|
+
include Remocon::InterpreterHelper
|
7
|
+
|
8
|
+
def initialize(opts)
|
9
|
+
@opts = opts
|
10
|
+
|
11
|
+
@project_id = ENV.fetch('FIREBASE_PROJECT_ID')
|
12
|
+
@token = ENV.fetch('REMOTE_CONFIG_ACCESS_TOKEN')
|
13
|
+
@url = "https://firebaseremoteconfig.googleapis.com/v1/projects/#{@project_id}/remoteConfig"
|
14
|
+
@dest_dir = File.join(@opts[:dest], @project_id) if @opts[:dest]
|
15
|
+
|
16
|
+
@cmd_opts = { validate_only: false }
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
if @dest_dir
|
21
|
+
FileUtils.mkdir_p(@dest_dir)
|
22
|
+
|
23
|
+
raw_hash = JSON.parse(raw_json).with_indifferent_access
|
24
|
+
|
25
|
+
raise 'etag cannot be fetched. please try again' unless @etag
|
26
|
+
|
27
|
+
conditions, parameters = [raw_hash[:conditions] || [], raw_hash[:parameters] || {}]
|
28
|
+
|
29
|
+
File.open(File.join(@dest_dir, "conditions.yml"), 'w+') do |f|
|
30
|
+
f.write(JSON.parse(Remocon::ConditionFileDumper.new(sort_conditions(conditions)).dump.to_json).to_yaml)
|
31
|
+
f.flush
|
32
|
+
end
|
33
|
+
|
34
|
+
File.open(File.join(@dest_dir, "parameters.yml"), 'w+') do |f|
|
35
|
+
f.write(JSON.parse(Remocon::ParameterFileDumper.new(sort_parameters(parameters)).dump.to_json).to_yaml)
|
36
|
+
f.flush
|
37
|
+
end
|
38
|
+
|
39
|
+
File.open(File.join(@dest_dir, "config.json"), 'w+') do |f|
|
40
|
+
f.write(JSON.pretty_generate({ conditions: sort_conditions(conditions), parameters: sort_parameters(parameters) }))
|
41
|
+
f.flush
|
42
|
+
end
|
43
|
+
|
44
|
+
File.open(File.join(@dest_dir, 'etag'), 'w+') do |f|
|
45
|
+
f.write(@etag)
|
46
|
+
f.flush
|
47
|
+
end
|
48
|
+
else
|
49
|
+
STDOUT.puts raw_json
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def raw_json
|
56
|
+
return @raw_json if @raw_json
|
57
|
+
|
58
|
+
@raw_json, @etag = open(@url, 'Authorization' => "Bearer #{@token}") do |io|
|
59
|
+
[io.read, io.meta['etag']]
|
60
|
+
end
|
61
|
+
|
62
|
+
@raw_json
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|