remocon 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|