etcenv 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c67fca7ce92b7e16d5365bdc969aa6e974fc6cc9
4
+ data.tar.gz: 99ad3db08fcc4e806a677cfe085bb475e1dceab7
5
+ SHA512:
6
+ metadata.gz: 3c1e239945d4bbbd0735f3f8ec451eab0da1ca607cdbfd9390543b10aec9718364ec51081fa521343d9d05607b55357fc73b8a2f2ff45a22f83d568edb26ea22
7
+ data.tar.gz: d5b380c6fa77885aa7d0d1d522b185184fa959a9f6864018b01696061451aeea356b47f84a09e9adf9ae93f1e0f2b848928016c8e2415cb6b322a5d67fef7479
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in etcenv.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Shota Fukumori (sora_h)
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,104 @@
1
+ # Etcenv: Dump etcd keys into dotenv file or docker env file
2
+
3
+ ## Installation
4
+
5
+ $ gem install etcenv
6
+
7
+ ## Simple Usage
8
+
9
+ ### Create directory and keys on etcd
10
+
11
+ ```
12
+ etcdctl mkdir /my-app
13
+ etcdctl set /my-app/AWESOME_SERVICE_CREDENTIAL xxxxxx
14
+ ```
15
+
16
+ ### Run `etcenv`
17
+
18
+ #### One-shot
19
+
20
+ This will output generated .env file to STDOUT.
21
+
22
+ ```
23
+ $ etcenv /my-app
24
+ AWESOME_SERVICE_CREDENTIAL=xxxxxx
25
+ ```
26
+
27
+ or
28
+
29
+ ```
30
+ $ etcenv -o .env /my-app
31
+ $ cat .env
32
+ AWESOME_SERVICE_CREDENTIAL=xxxxxx
33
+ ```
34
+
35
+ to save as file.
36
+
37
+ #### Continuously update
38
+
39
+ Etcenv also supports watching etcd server. In `--watch` mode, Etcenv updates dotenv file when value gets updated:
40
+
41
+ ```
42
+ $ etcenv --watch -o .env /my-env
43
+ ```
44
+
45
+ Also you can start it as daemon:
46
+
47
+ ```
48
+ $ etcenv --watch --daemon /path/to/pidfile.pid -o .env /my-env
49
+ ```
50
+
51
+ #### For docker
52
+
53
+ Use `--docker` flag to generate file for docker's `--env-file` option.
54
+
55
+ In docker mode, etcenv evaluates `${...}` expansion like dotenv do.
56
+
57
+ ## Options
58
+
59
+ ### etcd options
60
+
61
+ - `--etcd`: URL of etcd to connect to. Path in URL will be ignored.
62
+ - `--etcd-ca-file`: Path to CA certificate file (PEM) of etcd server.
63
+ - `--etcd-cert-file`: Path to client certificate file for etcd.
64
+ - `--etcd-key-file`: Path to private key file of client certificate file for etcd.
65
+
66
+ ## Advanced usage
67
+
68
+ ### Include other directory's variables
69
+
70
+ Set directory path to `_include`. Directories can be specified multiple, separated by comma.
71
+
72
+ ```
73
+ etcdctl mkdir /common
74
+ etcdctl set /common/COMMON_SECRET xxx
75
+ etcdctl set /my-app/_include /common
76
+ ```
77
+
78
+ Also, you can omit path of parent directory:
79
+
80
+ ```
81
+ etcdctl mkdir /envs/common
82
+ etcdctl set /envs/common/COMMON_SECRET xxx
83
+
84
+ etcdctl mkdir /envs/my-app
85
+ etcdctl set /envs/my-app/_include common
86
+ ```
87
+
88
+ - `_include` will be applied recursively (up to 10 times by default). If `_include` is looping, it'll be an error.
89
+ - For multiple `_include`, value for same key may be overwritten.
90
+ - If `a` includes `b`,`c` and `b` includes `d`, result for `a` will be: `d`, `b`, `c`, then `a`.
91
+
92
+ ## Development
93
+
94
+ After checking out the repo, run `scripts/setup` to install dependencies. Then, run `scripts/console` for an interactive prompt that will allow you to experiment.
95
+
96
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
97
+
98
+ ## Contributing
99
+
100
+ 1. Fork it ( https://github.com/sorah/etcenv/fork )
101
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
102
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
103
+ 4. Push to the branch (`git push origin my-new-feature`)
104
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/etcenv ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ require 'etcenv'
3
+ require 'etcenv/cli'
4
+
5
+ exit Etcenv::Cli.new(*ARGV).run
data/etcenv.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'etcenv/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "etcenv"
8
+ spec.version = Etcenv::VERSION
9
+ spec.authors = ["Shota Fukumori (sora_h)"]
10
+ spec.email = ["sorah@cookpad.com"]
11
+
12
+ spec.summary = %q{Dump etcd keys into dotenv file or docker env file}
13
+ spec.homepage = "https://github.com/sorah/etcenv"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "bin"
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec"
24
+
25
+ spec.add_dependency "etcd", ">= 0.3.0"
26
+ end
data/lib/etcenv/cli.rb ADDED
@@ -0,0 +1,170 @@
1
+ require 'optparse'
2
+ require 'thread'
3
+ require 'openssl'
4
+ require 'etcd'
5
+ require 'uri'
6
+ require 'etcenv/dockerenv_file'
7
+ require 'etcenv/dotenv_file'
8
+ require 'etcenv/watcher'
9
+
10
+ module Etcenv
11
+ class Cli
12
+ def initialize(*argv)
13
+ @argv = argv
14
+ @options = {
15
+ etcd: URI.parse("http://localhost:2379"),
16
+ format: DotenvFile,
17
+ perm: 0600,
18
+ mode: :oneshot,
19
+ }
20
+ parse!
21
+ end
22
+
23
+ attr_reader :argv, :options
24
+
25
+ def run
26
+ parse!
27
+
28
+ case options[:mode]
29
+ when :oneshot
30
+ oneshot
31
+ when :watch
32
+ watch
33
+ else
34
+ raise "[BUG] unknown mode"
35
+ end
36
+ end
37
+
38
+ def parse!
39
+ parser = OptionParser.new do |opts|
40
+ opts.banner = "Usage: etcenv [options] key ..."
41
+
42
+ opts.on("-h", "--help", "show this help") do
43
+ puts opts
44
+ exit 0
45
+ end
46
+
47
+ opts.on("--watch", "-w", "continuous update mode") do
48
+ options[:mode] = :watch
49
+ end
50
+
51
+ opts.on("--daemon", "-d", "daemonize") do
52
+ options[:daemonize] = true
53
+ end
54
+
55
+ opts.on("--pidfile PIDFILE", "-p PIDFILE", "pidfile to use when deamonizing") do |pidpath|
56
+ options[:pidfile] = pidpath
57
+ end
58
+
59
+ opts.on("-o PATH", "--output PATH", "save to speciifed file") do |path|
60
+ options[:output] = path
61
+ end
62
+
63
+ opts.on("-m MODE", "--mode MODE", "mode (permission) to use when creating --output file, in octal. Default: 0600") do |perm|
64
+ options[:perm] = perm.to_i(8)
65
+ end
66
+
67
+ opts.on("--docker", "use docker env-file format instead of dotenv.gem format") do
68
+ options[:format] = DockerenvFile
69
+ end
70
+
71
+ opts.on("--etcd URL", "URL of etcd to connect; Any paths are ignored.") do |url|
72
+ options[:etcd] = URI.parse(url)
73
+ end
74
+
75
+ opts.on("--etcd-ca-file PATH", "Path to CA certificate file (PEM) of etcd server") do |path|
76
+ options[:etcd_ca_file] = path
77
+ end
78
+
79
+ opts.on("--etcd-cert-file PATH", "Path to client certificate file (PEM) for etcd server") do |path|
80
+ options[:etcd_tls_cert] = OpenSSL::X509::Certificate.new(File.read(path))
81
+ end
82
+
83
+ opts.on("--etcd-key-file PATH", "Path to private key file for client certificate for etcd server") do |path|
84
+ options[:etcd_tls_key] = OpenSSL::PKey::RSA.new(File.read(path))
85
+ end
86
+ end
87
+ parser.parse!(argv)
88
+ end
89
+
90
+ def etcd
91
+ @etcd ||= Etcd.client(
92
+ host: options[:etcd].host,
93
+ port: options[:etcd].port,
94
+ use_ssl: options[:etcd].scheme == 'https',
95
+ ca_file: options[:etcd_ca_file],
96
+ ssl_cert: options[:etcd_tls_cert],
97
+ ssl_key: options[:etcd_tls_key],
98
+ )
99
+ end
100
+
101
+ def oneshot
102
+ if argv.empty?
103
+ $stderr.puts "error: no KEY specified. See --help for detail"
104
+ return 1
105
+ end
106
+ env = argv.inject(nil) do |env, key|
107
+ new_env = Environment.new(etcd, key).env
108
+ env ? env.merge(new_env) : new_env
109
+ end
110
+ dump_env(env)
111
+
112
+ 0
113
+ end
114
+
115
+ def watch
116
+ if argv.empty?
117
+ $stderr.puts "error: no KEY specified. See --help for detail"
118
+ return 1
119
+ end
120
+
121
+ if options[:daemonize]
122
+ $stderr.puts "Daemonizing"
123
+ Process.daemon(nil, true)
124
+ if options[:pidfile]
125
+ File.write options[:pidfile], "#{$$}\n"
126
+ end
127
+ end
128
+
129
+ envs = argv.map { |key| Environment.new(etcd, key) }
130
+
131
+ watchers = envs.map { |env| Watcher.new(env, verbose: true) }
132
+ Thread.abort_on_exception = true
133
+
134
+ dumper_ch = Queue.new
135
+ dumper = Thread.new do
136
+ loop do
137
+ $stderr.puts "[dumper] dumping env"
138
+ env = envs.inject(nil) do |result, env|
139
+ result ? result.merge(env.env) : env.env
140
+ end
141
+ dump_env(env)
142
+ dumper_ch.pop
143
+ end
144
+ end
145
+
146
+ watchers.map do |watcher|
147
+ Thread.new do
148
+ watcher.auto_reload_loop do
149
+ dumper_ch << true
150
+ end
151
+ end
152
+ end
153
+
154
+ loop { sleep 1 }
155
+ end
156
+
157
+ private
158
+
159
+ def dump_env(env)
160
+ env_file = options[:format].new(env)
161
+ if options[:output]
162
+ open(options[:output], "w", options[:perm]) do |io|
163
+ io.puts env_file.to_s
164
+ end
165
+ else
166
+ $stdout.puts env_file.to_s
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,25 @@
1
+ require 'etcenv/variable_expander'
2
+
3
+ module Etcenv
4
+ class DockerenvFile
5
+ def initialize(env)
6
+ @env = env
7
+ end
8
+
9
+ attr_reader :env
10
+
11
+ def expanded_env
12
+ VariableExpander.expand @env
13
+ end
14
+
15
+ def lines
16
+ expanded_env.map { |k,v| "#{k}=#{v}" }
17
+ end
18
+
19
+ def to_s
20
+ lines.join(?\n) + ?\n
21
+ end
22
+
23
+ private
24
+ end
25
+ end
@@ -0,0 +1,37 @@
1
+ require 'etcenv/variable_expander'
2
+
3
+ module Etcenv
4
+ class DotenvFile
5
+ def initialize(env)
6
+ @env = env
7
+ end
8
+
9
+ attr_reader :env
10
+
11
+ def expanded_env
12
+ VariableExpander.expand @env
13
+ end
14
+
15
+ def lines
16
+ expanded_env.map { |k, v| make_dotenv_line(k, v) }
17
+ end
18
+
19
+ def to_s
20
+ lines.join(?\n) + ?\n
21
+ end
22
+
23
+ private
24
+
25
+ SHOULD_QUOTE = /\n|"|#|\$/
26
+ def make_dotenv_line(k,v)
27
+ if v.match(SHOULD_QUOTE)
28
+ v.gsub!('"', '\"')
29
+ v.gsub!("\n", '\n')
30
+ v.gsub!(/\$([^(])/, '\$\1')
31
+ "#{k}=\"#{v}\""
32
+ else
33
+ "#{k}=#{v}"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,116 @@
1
+ module Etcenv
2
+ class Environment
3
+ class NotDirectory < StandardError; end
4
+ class DepthLimitError < StandardError; end
5
+ class LoopError < StandardError; end
6
+
7
+ INCLUDE_KEY = '_include'
8
+ MAX_DEPTH_DEFAULT = 10
9
+
10
+ def initialize(etcd, root_key, max_depth: MAX_DEPTH_DEFAULT)
11
+ @etcd = etcd
12
+ @root_key = root_key
13
+ @max_depth = max_depth
14
+ @lock = Mutex.new
15
+ load
16
+ end
17
+
18
+ attr_reader :root_key, :env, :etcd
19
+ attr_accessor :max_depth
20
+
21
+ def modified_indices
22
+ @modified_indices ||= {}
23
+ end
24
+
25
+ def load
26
+ @lock.synchronize do
27
+ flush
28
+ env = {}
29
+ includes.each do |name|
30
+ env.merge! fetch(name)
31
+ end
32
+ env.delete '_include'
33
+ @env = env
34
+ end
35
+ self
36
+ end
37
+
38
+ private
39
+
40
+ def flush
41
+ @env = {}
42
+ @includes = nil
43
+ @cache = {}
44
+ @modified_indices = {}
45
+ self
46
+ end
47
+
48
+ def includes
49
+ @includes ||= solve_include_order(root_key)
50
+ end
51
+
52
+ def default_prefix
53
+ root_key.sub(/^.*\//, '')
54
+ end
55
+
56
+ def resolve_key(key)
57
+ if key.start_with?('/')
58
+ key
59
+ else
60
+ default_prefix + '/' + key
61
+ end
62
+ end
63
+
64
+ def cache
65
+ @cache ||= {}
66
+ end
67
+
68
+ def fetch(name)
69
+ key = resolve_key(name)
70
+ return cache[key] if cache[key]
71
+
72
+ node = @etcd.get(key).node
73
+
74
+ unless node.dir
75
+ raise NotDirectory, "#{key} is not a directory"
76
+ end
77
+
78
+ dir = {}
79
+ index = 0
80
+
81
+ node.children.each do |child|
82
+ name = child.key.sub(/^.*\//, '')
83
+ if child.dir
84
+ next
85
+ else
86
+ dir[name] = child.value
87
+ index = [index, child.modified_index].max
88
+ end
89
+ end
90
+
91
+ modified_indices[key] = index
92
+ cache[key] = dir
93
+ end
94
+
95
+ def solve_include_order(name, path = [])
96
+ if path.include?(name)
97
+ raise LoopError, "Found an include loop at path: #{path.inspect}"
98
+ end
99
+
100
+ path = path + [name]
101
+ if max_depth < path.size
102
+ raise DepthLimitError, "Reached maximum depth (path: #{path.inspect})"
103
+ end
104
+
105
+ node = fetch(name)
106
+
107
+ if node[INCLUDE_KEY]
108
+ node[INCLUDE_KEY].split(/,\s*/).flat_map do |x|
109
+ solve_include_order(x, path)
110
+ end + [name]
111
+ else
112
+ [name]
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,16 @@
1
+ module Etcenv
2
+ module Utils
3
+ class << self
4
+ def uniq_with_keeping_first_appearance(array)
5
+ set = {}
6
+ result = []
7
+ array.each do |x|
8
+ next if set[x]
9
+ result.push x
10
+ set[x] = true
11
+ end
12
+ result
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,122 @@
1
+ require 'etcenv/utils'
2
+
3
+ module Etcenv
4
+ class VariableExpander
5
+ class LoopError < StandardError; end
6
+ class DepthLimitError < StandardError; end
7
+
8
+ MAX_DEPTH_DEFAULT = 50
9
+
10
+ def self.expand(variables, max = MAX_DEPTH_DEFAULT)
11
+ new(variables).expand(max)
12
+ end
13
+
14
+ def initialize(variables)
15
+ @variables = variables.dup.freeze
16
+ end
17
+
18
+ attr_reader :variables
19
+
20
+ def expand(max = MAX_DEPTH_DEFAULT)
21
+ detect_loop!
22
+
23
+ result = {}
24
+ solve_order(max).map do |x|
25
+ result[x] = single_expand(@variables[x], result)
26
+ end
27
+ result
28
+ end
29
+
30
+ def variable_with_deps
31
+ @variables.map { |k, v| [k, [v, detect_variables(v)]] }
32
+ end
33
+
34
+ def dependees_by_variable
35
+ @dependees_by_variable ||= variable_with_deps.inject({}) do |r, x|
36
+ k, v, deps = x[0], x[1][0], x[1][1]
37
+
38
+ deps.each do |dep|
39
+ r[dep] ||= []
40
+ r[dep] << k
41
+ end
42
+ r
43
+ end.freeze
44
+ end
45
+
46
+ def root_variables
47
+ @root_variables ||= variable_with_deps.inject([]) do |r, x|
48
+ k, v, deps = x[0], x[1][0], x[1][1]
49
+ if deps.empty?
50
+ r << k
51
+ end
52
+ r
53
+ end + (variables.keys - dependees_by_variable.keys)
54
+ end
55
+
56
+ def detect_loop!
57
+ if root_variables.empty?
58
+ raise LoopError, "there's no possible root variables (variables which don't have dependee)"
59
+ else
60
+ dependees_by_variable.each do |k, deps|
61
+ deps.each do |dep|
62
+ if (dependees_by_variable[dep] || []).include?(k)
63
+ raise LoopError, "There's a loop between $#{dep} and $#{k}"
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ def solve_order(max_depth = 10)
71
+ order = []
72
+ solve = nil
73
+ solve = ->(vs, depth = 0) do
74
+ raise DepthLimitError if depth.succ > max_depth
75
+ vs.each do |x|
76
+ order.concat solve.call(dependees_by_variable[x] || [], depth.succ) + [x]
77
+ end
78
+ end
79
+ solve.call(root_variables)
80
+
81
+ Utils.uniq_with_keeping_first_appearance(order).reverse
82
+ end
83
+
84
+ private
85
+
86
+ VARIABLE = /
87
+ (?<escape>\\?)
88
+ \$
89
+ (
90
+ {(?<name>[a-zA-Z0-9_]+)}
91
+ |
92
+ \g<name>
93
+ )
94
+ /x
95
+
96
+ def detect_variables(str)
97
+ result = []
98
+
99
+ pos = 0
100
+ while match = str.match(VARIABLE, pos)
101
+ pos = match.end(0)
102
+ next if match['escape'] == '\\'
103
+
104
+ result << match['name']
105
+ end
106
+
107
+ result
108
+ end
109
+
110
+ def single_expand(str, variables)
111
+ str.gsub(VARIABLE) do |variable|
112
+ match = $~
113
+
114
+ if match['escape'] == '\\'
115
+ variable[1..-1]
116
+ else
117
+ variables[match['name']].to_s
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,3 @@
1
+ module Etcenv
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,40 @@
1
+ require 'thread'
2
+
3
+ module Etcenv
4
+ class Watcher
5
+ def initialize(env, verbose: false)
6
+ @env = env
7
+ @verbose = verbose
8
+ end
9
+
10
+ attr_reader :env, :verbose
11
+
12
+ def etcd
13
+ env.etcd
14
+ end
15
+
16
+ def watch
17
+ ch = Queue.new
18
+ threads = env.modified_indices.map do |key, index|
19
+ Thread.new do
20
+ $stderr.puts "[watcher] waiting for change on #{key} (index: #{index.succ})" if verbose
21
+ etcd.watch(key, recursive: true, index: index.succ)
22
+ $stderr.puts "[watcher] dir #{key} has updated" if verbose
23
+ ch << key
24
+ end
25
+ end
26
+ report = ch.pop
27
+ threads.each(&:kill)
28
+ report
29
+ end
30
+
31
+ def auto_reload_loop
32
+ loop do
33
+ watch
34
+ $stderr.puts "[watcher] reloading env #{env.root_key}" if verbose
35
+ env.load
36
+ yield env if block_given?
37
+ end
38
+ end
39
+ end
40
+ end
data/lib/etcenv.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'etcenv/version'
2
+ require 'etcenv/environment'
3
+ require 'etcenv/variable_expander'
4
+ require 'etcenv/dockerenv_file'
5
+ require 'etcenv/dotenv_file'
data/scripts/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "etcenv"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/scripts/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: etcenv
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Shota Fukumori (sora_h)
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: etcd
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 0.3.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 0.3.0
69
+ description:
70
+ email:
71
+ - sorah@cookpad.com
72
+ executables:
73
+ - etcenv
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - Gemfile
81
+ - LICENSE.txt
82
+ - README.md
83
+ - Rakefile
84
+ - bin/etcenv
85
+ - etcenv.gemspec
86
+ - lib/etcenv.rb
87
+ - lib/etcenv/cli.rb
88
+ - lib/etcenv/dockerenv_file.rb
89
+ - lib/etcenv/dotenv_file.rb
90
+ - lib/etcenv/environment.rb
91
+ - lib/etcenv/utils.rb
92
+ - lib/etcenv/variable_expander.rb
93
+ - lib/etcenv/version.rb
94
+ - lib/etcenv/watcher.rb
95
+ - scripts/console
96
+ - scripts/setup
97
+ homepage: https://github.com/sorah/etcenv
98
+ licenses:
99
+ - MIT
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubyforge_project:
117
+ rubygems_version: 2.2.2
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: Dump etcd keys into dotenv file or docker env file
121
+ test_files: []