populate-env 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/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +136 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/populate-env +5 -0
- data/lib/populate_env.rb +12 -0
- data/lib/populate_env/attribute.rb +14 -0
- data/lib/populate_env/attribute_definition.rb +21 -0
- data/lib/populate_env/cli.rb +14 -0
- data/lib/populate_env/cli/heroku_options.rb +66 -0
- data/lib/populate_env/cli/runner.rb +60 -0
- data/lib/populate_env/formatters/env_shell_section.rb +27 -0
- data/lib/populate_env/heroku.rb +9 -0
- data/lib/populate_env/heroku/attribute_compilation.rb +103 -0
- data/lib/populate_env/heroku/compilation.rb +38 -0
- data/lib/populate_env/heroku/manifest.rb +33 -0
- data/lib/populate_env/heroku/options.rb +42 -0
- data/lib/populate_env/heroku/remote_config.rb +66 -0
- data/lib/populate_env/missing_prompt.rb +15 -0
- data/lib/populate_env/shell_command.rb +7 -0
- data/lib/populate_env/version.rb +3 -0
- data/populate-env.gemspec +26 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c899bcaa6bfb1b2ee92df5f11510b8769410b55f
|
4
|
+
data.tar.gz: f316c621e3ed6846f317f57f9ad326ef202c55d7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e559cca288de0ff277aafcb670531025da96f2330226976efdd8b213cd84aff6a7a28a1a3025f1384b5a165b12f7c27618213ce6e6d5d3b708c50e7d673b934a
|
7
|
+
data.tar.gz: 125991f277d55a000e684e9247917bf67bab418a0c5f18c3fa05f722d052877d0a7fb47c16930885eb347f277612db20b71c1661c9a6914d1db9d045920cc1a2
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at pete@metanation.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Pete Nicholls
|
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,136 @@
|
|
1
|
+
# populate-env
|
2
|
+
|
3
|
+
A command-line tool for populating environment variables.
|
4
|
+
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
gem install populate-env
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
### Heroku
|
13
|
+
|
14
|
+
Heroku apps can describe which environment variables they need via an [app.json manifest](https://blog.heroku.com/introducing_the_app_json_application_manifest):
|
15
|
+
|
16
|
+
```json
|
17
|
+
{
|
18
|
+
"env": {
|
19
|
+
"BRAND_COLOUR": "smaragdine",
|
20
|
+
"AMAZING_WEB_SERVICES_TOKEN": {
|
21
|
+
"description": "A required token for interacting with Amazing Web Services."
|
22
|
+
},
|
23
|
+
"WEB_CONCURRENCY": {
|
24
|
+
"description": "The number of processes to run.",
|
25
|
+
"value": "5"
|
26
|
+
},
|
27
|
+
"LEATHER_SEATS": {
|
28
|
+
"description": "Optional flag to enable leather seats.",
|
29
|
+
"required": false
|
30
|
+
},
|
31
|
+
"SECRET_TOKEN": {
|
32
|
+
"description": "A secret key for verifying the integrity of signed cookies.",
|
33
|
+
"generator": "secret"
|
34
|
+
}
|
35
|
+
},
|
36
|
+
"environments": {
|
37
|
+
"test": {
|
38
|
+
"AMAZING_WEB_SERVICES_TOKEN": "mock"
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
42
|
+
```
|
43
|
+
|
44
|
+
Given this file, running the following command will generate a `.env` file:
|
45
|
+
|
46
|
+
$ populate-env heroku
|
47
|
+
|
48
|
+
BRAND_COLOUR: "smaragdine"
|
49
|
+
=> using default from app.json
|
50
|
+
|
51
|
+
AMAZING_WEB_SERVICES_TOKEN: "token-from-heroku-app"
|
52
|
+
=> using value from detected Heroku remote "dev"
|
53
|
+
|
54
|
+
WEB_CONCURRENCY: "5"
|
55
|
+
=> using default from app.json
|
56
|
+
|
57
|
+
LEATHER_SEATS: (skipped)
|
58
|
+
=> no value available
|
59
|
+
|
60
|
+
SECRET_TOKEN: "1d8505..."
|
61
|
+
=> generated secret (32 chars)
|
62
|
+
|
63
|
+
WEB_CONCURRENCY: "5"
|
64
|
+
=> using default from app.json
|
65
|
+
|
66
|
+
The resulting `.env` file looks like this:
|
67
|
+
|
68
|
+
```shell
|
69
|
+
BRAND_COLOUR=smaragdine
|
70
|
+
|
71
|
+
# A required token for interacting with Amazing Web Services.
|
72
|
+
AMAZING_WEB_SERVICES_TOKEN=token-from-heroku-app
|
73
|
+
|
74
|
+
# The number of processes to run.
|
75
|
+
WEB_CONCURRENCY=5
|
76
|
+
|
77
|
+
# Optional flag to enable leather seats.
|
78
|
+
# LEATHER_SEATS=
|
79
|
+
|
80
|
+
# A secret key for verifying the integrity of signed cookies.
|
81
|
+
SECRET_TOKEN=1d8505fa8172fae3b17f5e57568406b8
|
82
|
+
```
|
83
|
+
|
84
|
+
Any required environment variable not currently in your ENV will be populated by trying each of the following:
|
85
|
+
|
86
|
+
1. Using the default value, as specified in your `app.json`
|
87
|
+
2. Generating a pseudorandom secret (for environment variables marked with `generator: secret`)
|
88
|
+
3. Using the values from your remote Heroku instance
|
89
|
+
4. Prompting you for a missing, required value
|
90
|
+
|
91
|
+
The command above is equivalent to the following options:
|
92
|
+
|
93
|
+
populate-env heroku \
|
94
|
+
--manifest app.json \
|
95
|
+
--manifest-environment production \
|
96
|
+
--destination .env \
|
97
|
+
--skip-local-env \
|
98
|
+
--heroku-config \
|
99
|
+
--generate-secrets \
|
100
|
+
--prompt-missing
|
101
|
+
|
102
|
+
#### Remote Heroku config
|
103
|
+
|
104
|
+
If your project has a git remote with `heroku.com` in the URL, `populate-env`
|
105
|
+
will detect it and use it to retrieve remote configuration. If you don't want
|
106
|
+
this behaviour, you can skip it with:
|
107
|
+
|
108
|
+
populate-env heroku --no-heroku-config
|
109
|
+
|
110
|
+
If you have multiple Heroku remotes or want to specify your Heroku app
|
111
|
+
explicitly, you can use either:
|
112
|
+
|
113
|
+
populate-env heroku --heroku-remote staging # or
|
114
|
+
populate-env heroku --heroku-app spronking-wildebeest-42
|
115
|
+
|
116
|
+
For full options, run:
|
117
|
+
|
118
|
+
populate-env heroku --help
|
119
|
+
|
120
|
+
## Development
|
121
|
+
|
122
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
123
|
+
|
124
|
+
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`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
125
|
+
|
126
|
+
## Contributing
|
127
|
+
|
128
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Aupajo/populate-env. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
129
|
+
|
130
|
+
## License
|
131
|
+
|
132
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
133
|
+
|
134
|
+
## Code of Conduct
|
135
|
+
|
136
|
+
Everyone interacting in the Populate::Env project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/Aupajo/populate-env/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "populate/env"
|
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(__FILE__)
|
data/bin/setup
ADDED
data/exe/populate-env
ADDED
data/lib/populate_env.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'populate_env/attribute'
|
3
|
+
require 'populate_env/attribute_definition'
|
4
|
+
require 'populate_env/shell_command'
|
5
|
+
require 'populate_env/missing_prompt'
|
6
|
+
require 'populate_env/formatters/env_shell_section'
|
7
|
+
require 'populate_env/heroku'
|
8
|
+
require 'populate_env/heroku/attribute_compilation'
|
9
|
+
require 'populate_env/heroku/compilation'
|
10
|
+
require 'populate_env/heroku/options'
|
11
|
+
require 'populate_env/heroku/manifest'
|
12
|
+
require 'populate_env/heroku/remote_config'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module PopulateEnv
|
2
|
+
class AttributeDefinition
|
3
|
+
attr_reader :name, :default, :generator, :description
|
4
|
+
|
5
|
+
def initialize(name:, default: nil, generator: nil, description: nil, required: true)
|
6
|
+
@name = name.to_s.upcase
|
7
|
+
@default = default
|
8
|
+
@generator = generator
|
9
|
+
@description = description
|
10
|
+
@required = required
|
11
|
+
end
|
12
|
+
|
13
|
+
def required?
|
14
|
+
@required
|
15
|
+
end
|
16
|
+
|
17
|
+
def optional?
|
18
|
+
!required?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'populate_env/version'
|
3
|
+
require 'populate_env/cli/runner'
|
4
|
+
require 'populate_env/cli/heroku_options'
|
5
|
+
|
6
|
+
module PopulateEnv
|
7
|
+
module CLI
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def start(*args)
|
11
|
+
Runner.new(*args).run
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module PopulateEnv
|
4
|
+
module CLI
|
5
|
+
module HerokuOptions
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def parse(argv, command:, options: PopulateEnv::Heroku::Options.new)
|
9
|
+
parser = OptionParser.new
|
10
|
+
|
11
|
+
parser.accept(Pathname) { |path| Pathname(path) }
|
12
|
+
|
13
|
+
parser.banner = "Usage: #{command} [options]"
|
14
|
+
|
15
|
+
description = "Path to the app.json schema (defaults to #{options.manifest})"
|
16
|
+
parser.on("--manifest FILE", Pathname, description) do |value|
|
17
|
+
options.manifest = value
|
18
|
+
end
|
19
|
+
|
20
|
+
description = "File to write (defaults to #{options.destination})"
|
21
|
+
parser.on("--destination FILE", Pathname, description) do |value|
|
22
|
+
options.destination = value
|
23
|
+
end
|
24
|
+
|
25
|
+
description = "Environment to read from the app.json manifest (defaults to #{options.manifest_environment})"
|
26
|
+
parser.on("-e", "--manifest-environment ENVIRONMENT", description) do |value|
|
27
|
+
options.manifest_environment = value
|
28
|
+
end
|
29
|
+
|
30
|
+
description = "Skip environment variables currently set in your shell (defaults to #{options.skip_local_env})"
|
31
|
+
parser.on("--[no-]skip-local-env", description) do |value|
|
32
|
+
options.skip_local_env = value
|
33
|
+
end
|
34
|
+
|
35
|
+
description = "Populate missing environment variables from Heroku (defaults to #{options.use_heroku_config})"
|
36
|
+
parser.on("--[no-]heroku-config", description) do |value|
|
37
|
+
options.use_heroku_config = value
|
38
|
+
end
|
39
|
+
|
40
|
+
description = 'Heroku remote to use for reading config'
|
41
|
+
parser.on("--heroku-remote GIT_REMOTE", description) do |value|
|
42
|
+
options.heroku_remote = value
|
43
|
+
end
|
44
|
+
|
45
|
+
description = 'Heroku app to use for reading config'
|
46
|
+
parser.on("--heroku-app APP_NAME", description) do |value|
|
47
|
+
options.heroku_app = value
|
48
|
+
end
|
49
|
+
|
50
|
+
description = 'Whether to generate secrets'
|
51
|
+
parser.on("--[no-]generate-secrets", description) do |value|
|
52
|
+
options.generate_secrets = value
|
53
|
+
end
|
54
|
+
|
55
|
+
description = "Prompt the user for missing environment variables (defaults to #{options.prompt_missing})"
|
56
|
+
parser.on("--[no-]prompt-missing", description) do |value|
|
57
|
+
options.prompt_missing = value
|
58
|
+
end
|
59
|
+
|
60
|
+
parser.parse!(argv)
|
61
|
+
|
62
|
+
options
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module PopulateEnv
|
2
|
+
module CLI
|
3
|
+
class Runner
|
4
|
+
attr_reader :argv, :output, :executable
|
5
|
+
|
6
|
+
def initialize(argv: ARGV, output: $stdout, executable: File.basename($0))
|
7
|
+
@argv, @output, @executable = Array(argv), output, executable
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
send(subcommand)
|
12
|
+
end
|
13
|
+
|
14
|
+
def help
|
15
|
+
output.puts option_parser.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
def version
|
19
|
+
output.puts "#{executable} version #{VERSION}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def heroku
|
23
|
+
options = CLI::HerokuOptions.parse(argv, command: "#{executable} heroku")
|
24
|
+
PopulateEnv::Heroku.call(options)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def option_parser
|
30
|
+
@option_parser ||= OptionParser.new do |parser|
|
31
|
+
parser.banner = <<~BANNER
|
32
|
+
Usage:
|
33
|
+
#{executable} COMMAND [options]
|
34
|
+
|
35
|
+
Commands:
|
36
|
+
heroku
|
37
|
+
|
38
|
+
Options:
|
39
|
+
BANNER
|
40
|
+
|
41
|
+
description = 'Show the help (combine with a command for full options)'
|
42
|
+
parser.on('-h', '--help', description)
|
43
|
+
|
44
|
+
description = "Show the current #{executable} version (#{VERSION})"
|
45
|
+
parser.on('-v', '--version', description) { @subcommand = :version }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_options!
|
50
|
+
option_parser.parse!(argv)
|
51
|
+
rescue OptionParser::InvalidOption
|
52
|
+
end
|
53
|
+
|
54
|
+
def subcommand(fallback: :help)
|
55
|
+
method_name = @subcommand || argv.shift || fallback
|
56
|
+
respond_to?(method_name) ? method_name : fallback
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module PopulateEnv
|
2
|
+
module Formatters
|
3
|
+
class EnvShellSection
|
4
|
+
attr_reader :attribute
|
5
|
+
|
6
|
+
def initialize(attribute)
|
7
|
+
@attribute = attribute
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
output = ''
|
12
|
+
|
13
|
+
if attribute.description
|
14
|
+
attribute.description.each_line do |line|
|
15
|
+
output << "# #{line}\n"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
if attribute.optional? && attribute.value.nil?
|
20
|
+
output << "# "
|
21
|
+
end
|
22
|
+
|
23
|
+
output << "#{attribute.name}=#{attribute.value}\n"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module PopulateEnv
|
2
|
+
module Heroku
|
3
|
+
class AttributeCompilation
|
4
|
+
attr_reader :definition, :options, :remote_config
|
5
|
+
|
6
|
+
def initialize(definition, options, remote_config)
|
7
|
+
@definition, @options, @remote_config = definition, options, remote_config
|
8
|
+
end
|
9
|
+
|
10
|
+
def perform
|
11
|
+
options.output.print "#{definition.name}:"
|
12
|
+
|
13
|
+
attempt_to_populate_value
|
14
|
+
|
15
|
+
unless @skip_log
|
16
|
+
options.output.puts " #{@status_for_output || @value.inspect}"
|
17
|
+
options.output.puts " => #{@explanation}"
|
18
|
+
options.output.puts
|
19
|
+
end
|
20
|
+
|
21
|
+
Attribute.new(definition, @value) unless @skip
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def attempt_to_populate_value
|
27
|
+
if !options.skip_local_env && options.local_env[definition.name]
|
28
|
+
skip_due_to_local_env_var
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
if definition.default
|
33
|
+
use_manifest_default
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
if definition.generator == 'secret' && options.generate_secrets
|
38
|
+
generate_secret
|
39
|
+
return
|
40
|
+
end
|
41
|
+
|
42
|
+
if optional?
|
43
|
+
skip_optional
|
44
|
+
return
|
45
|
+
end
|
46
|
+
|
47
|
+
if options.use_heroku_config
|
48
|
+
attempt_to_read_from_heroku_config
|
49
|
+
return if @value
|
50
|
+
end
|
51
|
+
|
52
|
+
if options.prompt_missing
|
53
|
+
prompt_user_for_missing_value
|
54
|
+
return if @value
|
55
|
+
end
|
56
|
+
|
57
|
+
fail "Required environment variable #{definition.name} could not be populated!"
|
58
|
+
end
|
59
|
+
|
60
|
+
def required?
|
61
|
+
definition.required?
|
62
|
+
end
|
63
|
+
|
64
|
+
def optional?
|
65
|
+
definition.optional?
|
66
|
+
end
|
67
|
+
|
68
|
+
def generate_secret
|
69
|
+
@value = options.secret_generator.call
|
70
|
+
@explanation = "generated secret"
|
71
|
+
end
|
72
|
+
|
73
|
+
def skip_optional
|
74
|
+
@status_for_output = "(skipped)"
|
75
|
+
@explanation = "no value available"
|
76
|
+
end
|
77
|
+
|
78
|
+
def skip_due_to_local_env_var
|
79
|
+
@status_for_output = "(skipped)"
|
80
|
+
@explanation = "found in local ENV"
|
81
|
+
@skip = true
|
82
|
+
end
|
83
|
+
|
84
|
+
def use_manifest_default
|
85
|
+
@value = definition.default
|
86
|
+
@explanation = "using default from #{options.manifest.basename}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def attempt_to_read_from_heroku_config
|
90
|
+
@value = remote_config[definition.name]
|
91
|
+
|
92
|
+
if @value
|
93
|
+
@explanation = "using value from Heroku (#{remote_config.heroku_app_flag.inspect})"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def prompt_user_for_missing_value
|
98
|
+
@value = MissingPrompt.call(definition, options)
|
99
|
+
@skip_log = true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module PopulateEnv
|
2
|
+
module Heroku
|
3
|
+
class Compilation
|
4
|
+
attr_reader :options
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def manifest
|
11
|
+
Manifest.new(options.manifest)
|
12
|
+
end
|
13
|
+
|
14
|
+
def attribute_definitions
|
15
|
+
manifest.attribute_definitions_for(options.manifest_environment)
|
16
|
+
end
|
17
|
+
|
18
|
+
def remote_config
|
19
|
+
@remote_config ||= RemoteConfig.new(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def sections
|
23
|
+
attribute_definitions.map do |definition|
|
24
|
+
attribute = AttributeCompilation.new(definition, options, remote_config).perform
|
25
|
+
Formatters::EnvShellSection.new(attribute) if attribute
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def content
|
30
|
+
sections.compact.join("\n")
|
31
|
+
end
|
32
|
+
|
33
|
+
def perform
|
34
|
+
options.destination.write(content)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module PopulateEnv
|
4
|
+
module Heroku
|
5
|
+
class Manifest
|
6
|
+
def initialize(path)
|
7
|
+
@path = path
|
8
|
+
end
|
9
|
+
|
10
|
+
def attribute_definitions_for(environment)
|
11
|
+
begin
|
12
|
+
data = JSON.parse(@path.read, symbolize_names: true)
|
13
|
+
env_vars = data.fetch(:env, {})
|
14
|
+
env_vars.merge!(data.dig(:environments, environment.to_sym, :env) || {})
|
15
|
+
|
16
|
+
env_vars.map do |key, value|
|
17
|
+
if value.is_a?(Hash)
|
18
|
+
default = value.delete(:value)
|
19
|
+
value.merge!(name: key, default: default)
|
20
|
+
AttributeDefinition.new(value)
|
21
|
+
else
|
22
|
+
AttributeDefinition.new(name: key, default: value)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
rescue Errno::ENOENT
|
26
|
+
fail "Manifest file #{@path.to_s.inspect} not found"
|
27
|
+
rescue JSON::ParserError
|
28
|
+
fail "Could not parse JSON in #{@path.to_s.inspect}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module PopulateEnv
|
4
|
+
module Heroku
|
5
|
+
class Options
|
6
|
+
DEFAULTS = {
|
7
|
+
manifest_environment: 'production',
|
8
|
+
manifest: 'app.json',
|
9
|
+
destination: '.env',
|
10
|
+
generate_secrets: true,
|
11
|
+
secret_generator: SecureRandom.method(:hex),
|
12
|
+
use_heroku_config: true,
|
13
|
+
heroku_app: nil,
|
14
|
+
heroku_remote: nil,
|
15
|
+
skip_local_env: false,
|
16
|
+
prompt_missing: true,
|
17
|
+
output: $stdout,
|
18
|
+
input: $stdin,
|
19
|
+
local_env: ENV
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
attr_accessor *DEFAULTS.keys
|
23
|
+
|
24
|
+
def initialize(**attrs)
|
25
|
+
attrs = DEFAULTS.merge(attrs)
|
26
|
+
attrs.each { |attr, value| public_send("#{attr}=", value) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def manifest=(value)
|
30
|
+
@manifest = Pathname(value)
|
31
|
+
end
|
32
|
+
|
33
|
+
def destination=(value)
|
34
|
+
@destination = Pathname(value)
|
35
|
+
end
|
36
|
+
|
37
|
+
def manifest_environment=(value)
|
38
|
+
@manifest_environment = value.to_sym
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module PopulateEnv
|
4
|
+
module Heroku
|
5
|
+
class RemoteConfig
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](name)
|
13
|
+
data[name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def heroku_app_flag
|
17
|
+
@heroku_app_flag ||= determine_heroku_app_flag!
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def data
|
23
|
+
@data ||= fetch_data!
|
24
|
+
end
|
25
|
+
|
26
|
+
def fetch_data!
|
27
|
+
if heroku_app_flag
|
28
|
+
command_output = ShellCommand.run("heroku config --json #{heroku_app_flag}")
|
29
|
+
|
30
|
+
begin
|
31
|
+
JSON.parse(command_output)
|
32
|
+
rescue JSON::ParserError
|
33
|
+
fail "Could not parse JSON in `#{command}`:\n#{command_output}"
|
34
|
+
end
|
35
|
+
else
|
36
|
+
{}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def determine_heroku_app_flag!
|
41
|
+
if options.heroku_app
|
42
|
+
heroku_app_flag = "--app #{options.heroku_app}"
|
43
|
+
elsif options.heroku_remote
|
44
|
+
heroku_app_flag = "--remote #{options.heroku_remote}"
|
45
|
+
else
|
46
|
+
fail 'More than one Heroku remote' if git_remotes.length > 1
|
47
|
+
|
48
|
+
if git_remotes.length == 1
|
49
|
+
heroku_app_flag = "--remote #{git_remotes.first}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def git_remotes
|
55
|
+
@git_remotes ||= begin
|
56
|
+
raw_remotes = ShellCommand.run('git remote --verbose').split("\n")
|
57
|
+
|
58
|
+
raw_remotes.each_with_object(Set.new) do |line, remotes|
|
59
|
+
remote, url, _ = line.split
|
60
|
+
remotes << remote if url.include?('heroku.com')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module PopulateEnv
|
2
|
+
module MissingPrompt
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def call(definition, options)
|
6
|
+
prompt = ''
|
7
|
+
prompt << "\n #{definition.description}\n\n" if definition.description
|
8
|
+
prompt << " => Please provide a value for #{definition.name}: "
|
9
|
+
|
10
|
+
options.output.print prompt
|
11
|
+
|
12
|
+
options.input.gets.strip
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -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 "populate_env/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "populate-env"
|
8
|
+
spec.version = PopulateEnv::VERSION
|
9
|
+
spec.authors = ["Pete Nicholls"]
|
10
|
+
spec.email = ["aupajo@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A command-line tool for populating environment variables}
|
13
|
+
spec.homepage = "https://github.com/Aupajo/populate-env"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: populate-env
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pete Nicholls
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-12 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: '1.15'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.15'
|
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: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- aupajo@gmail.com
|
58
|
+
executables:
|
59
|
+
- populate-env
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- ".rspec"
|
65
|
+
- ".travis.yml"
|
66
|
+
- CODE_OF_CONDUCT.md
|
67
|
+
- Gemfile
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- bin/console
|
72
|
+
- bin/setup
|
73
|
+
- exe/populate-env
|
74
|
+
- lib/populate_env.rb
|
75
|
+
- lib/populate_env/attribute.rb
|
76
|
+
- lib/populate_env/attribute_definition.rb
|
77
|
+
- lib/populate_env/cli.rb
|
78
|
+
- lib/populate_env/cli/heroku_options.rb
|
79
|
+
- lib/populate_env/cli/runner.rb
|
80
|
+
- lib/populate_env/formatters/env_shell_section.rb
|
81
|
+
- lib/populate_env/heroku.rb
|
82
|
+
- lib/populate_env/heroku/attribute_compilation.rb
|
83
|
+
- lib/populate_env/heroku/compilation.rb
|
84
|
+
- lib/populate_env/heroku/manifest.rb
|
85
|
+
- lib/populate_env/heroku/options.rb
|
86
|
+
- lib/populate_env/heroku/remote_config.rb
|
87
|
+
- lib/populate_env/missing_prompt.rb
|
88
|
+
- lib/populate_env/shell_command.rb
|
89
|
+
- lib/populate_env/version.rb
|
90
|
+
- populate-env.gemspec
|
91
|
+
homepage: https://github.com/Aupajo/populate-env
|
92
|
+
licenses:
|
93
|
+
- MIT
|
94
|
+
metadata: {}
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 2.6.11
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: A command-line tool for populating environment variables
|
115
|
+
test_files: []
|