heroku-config 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 +16 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +86 -0
- data/Guardfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +46 -0
- data/Rakefile +14 -0
- data/exe/heroku-config +14 -0
- data/heroku-config.gemspec +34 -0
- data/lib/heroku-config.rb +1 -0
- data/lib/heroku_config/autoloader.rb +22 -0
- data/lib/heroku_config/aws_key.rb +106 -0
- data/lib/heroku_config/aws_rotate.rb +19 -0
- data/lib/heroku_config/aws_services.rb +18 -0
- data/lib/heroku_config/base.rb +10 -0
- data/lib/heroku_config/cli.rb +29 -0
- data/lib/heroku_config/command.rb +82 -0
- data/lib/heroku_config/completer/script.rb +6 -0
- data/lib/heroku_config/completer/script.sh +10 -0
- data/lib/heroku_config/completer.rb +159 -0
- data/lib/heroku_config/config.rb +72 -0
- data/lib/heroku_config/help/aws_rotate.md +18 -0
- data/lib/heroku_config/help/completion.md +20 -0
- data/lib/heroku_config/help/completion_script.md +3 -0
- data/lib/heroku_config/help.rb +9 -0
- data/lib/heroku_config/version.rb +3 -0
- data/lib/heroku_config.rb +11 -0
- data/spec/lib/cli_spec.rb +13 -0
- data/spec/spec_helper.rb +29 -0
- metadata +245 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8d09f67aa9118f360968ebac3deda4367589a820fff3f58c1cbb55dc8f12da0f
|
|
4
|
+
data.tar.gz: 7fbda965589b2a43c44d928e893a579f340db81b7fbde46791fb27c65377ce0c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: eed2ef3cffa39a324c910771da3ccd45ab8d98856a36d3d45b380a93572fbf20cf0212eba620b7058caf9745e59f53059b4eea0b15585e35da9c44174722d1b3
|
|
7
|
+
data.tar.gz: 54399e1010245b85cdc57164d634b529ff6aaa79b93d326127db45588f699bbf86b5074c273e01261c47ef635ac082b303842ad767228f5aee7da771b7ef12fa
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
heroku-config (0.1.0)
|
|
5
|
+
activesupport
|
|
6
|
+
aws-sdk-core
|
|
7
|
+
aws-sdk-iam
|
|
8
|
+
memoist
|
|
9
|
+
rainbow
|
|
10
|
+
thor
|
|
11
|
+
zeitwerk
|
|
12
|
+
|
|
13
|
+
GEM
|
|
14
|
+
remote: https://rubygems.org/
|
|
15
|
+
specs:
|
|
16
|
+
activesupport (6.0.1)
|
|
17
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
18
|
+
i18n (>= 0.7, < 2)
|
|
19
|
+
minitest (~> 5.1)
|
|
20
|
+
tzinfo (~> 1.1)
|
|
21
|
+
zeitwerk (~> 2.2)
|
|
22
|
+
aws-eventstream (1.0.3)
|
|
23
|
+
aws-partitions (1.240.0)
|
|
24
|
+
aws-sdk-core (3.78.0)
|
|
25
|
+
aws-eventstream (~> 1.0, >= 1.0.2)
|
|
26
|
+
aws-partitions (~> 1, >= 1.239.0)
|
|
27
|
+
aws-sigv4 (~> 1.1)
|
|
28
|
+
jmespath (~> 1.0)
|
|
29
|
+
aws-sdk-iam (1.31.0)
|
|
30
|
+
aws-sdk-core (~> 3, >= 3.71.0)
|
|
31
|
+
aws-sigv4 (~> 1.1)
|
|
32
|
+
aws-sigv4 (1.1.0)
|
|
33
|
+
aws-eventstream (~> 1.0, >= 1.0.2)
|
|
34
|
+
byebug (11.0.1)
|
|
35
|
+
cli_markdown (0.1.0)
|
|
36
|
+
codeclimate-test-reporter (1.0.9)
|
|
37
|
+
simplecov (<= 0.13)
|
|
38
|
+
concurrent-ruby (1.1.5)
|
|
39
|
+
diff-lcs (1.3)
|
|
40
|
+
docile (1.1.5)
|
|
41
|
+
i18n (1.7.0)
|
|
42
|
+
concurrent-ruby (~> 1.0)
|
|
43
|
+
jmespath (1.4.0)
|
|
44
|
+
json (2.2.0)
|
|
45
|
+
memoist (0.16.1)
|
|
46
|
+
minitest (5.13.0)
|
|
47
|
+
rainbow (3.0.0)
|
|
48
|
+
rake (13.0.1)
|
|
49
|
+
rspec (3.9.0)
|
|
50
|
+
rspec-core (~> 3.9.0)
|
|
51
|
+
rspec-expectations (~> 3.9.0)
|
|
52
|
+
rspec-mocks (~> 3.9.0)
|
|
53
|
+
rspec-core (3.9.0)
|
|
54
|
+
rspec-support (~> 3.9.0)
|
|
55
|
+
rspec-expectations (3.9.0)
|
|
56
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
57
|
+
rspec-support (~> 3.9.0)
|
|
58
|
+
rspec-mocks (3.9.0)
|
|
59
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
60
|
+
rspec-support (~> 3.9.0)
|
|
61
|
+
rspec-support (3.9.0)
|
|
62
|
+
simplecov (0.13.0)
|
|
63
|
+
docile (~> 1.1.0)
|
|
64
|
+
json (>= 1.8, < 3)
|
|
65
|
+
simplecov-html (~> 0.10.0)
|
|
66
|
+
simplecov-html (0.10.2)
|
|
67
|
+
thor (0.20.3)
|
|
68
|
+
thread_safe (0.3.6)
|
|
69
|
+
tzinfo (1.2.5)
|
|
70
|
+
thread_safe (~> 0.1)
|
|
71
|
+
zeitwerk (2.2.1)
|
|
72
|
+
|
|
73
|
+
PLATFORMS
|
|
74
|
+
ruby
|
|
75
|
+
|
|
76
|
+
DEPENDENCIES
|
|
77
|
+
bundler
|
|
78
|
+
byebug
|
|
79
|
+
cli_markdown
|
|
80
|
+
codeclimate-test-reporter
|
|
81
|
+
heroku-config!
|
|
82
|
+
rake
|
|
83
|
+
rspec
|
|
84
|
+
|
|
85
|
+
BUNDLED WITH
|
|
86
|
+
2.0.2
|
data/Guardfile
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
guard "bundler", cmd: "bundle" do
|
|
2
|
+
watch("Gemfile")
|
|
3
|
+
watch(/^.+\.gemspec/)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
|
7
|
+
require "guard/rspec/dsl"
|
|
8
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
|
9
|
+
|
|
10
|
+
# RSpec files
|
|
11
|
+
rspec = dsl.rspec
|
|
12
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
|
13
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
|
14
|
+
watch(rspec.spec_files)
|
|
15
|
+
|
|
16
|
+
# Ruby files
|
|
17
|
+
ruby = dsl.ruby
|
|
18
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
|
19
|
+
end
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2019 Tung Nguyen
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# HerokuConfig
|
|
2
|
+
|
|
3
|
+
[](http://badge.fury.io/rb/heroku-config)
|
|
4
|
+
|
|
5
|
+
Easily rotate AWS keys and heroku configs.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
heroku-config aws-rotate APP
|
|
10
|
+
|
|
11
|
+
## Example with Output
|
|
12
|
+
|
|
13
|
+
$ heroku-config aws-rotate protected-oasis-24054
|
|
14
|
+
=> heroku config:get AWS_ACCESS_KEY_ID -a protected-oasis-24054
|
|
15
|
+
Updating access key for user: bob
|
|
16
|
+
Created new access key: AKIAXZ6ODJLQQEXAMPLE
|
|
17
|
+
=> heroku config:set AWS_ACCESS_KEY_ID=AKIAXZ6ODJLQQEXAMPLE AWS_SECRET_ACCESS_KEY=sp4gmsuif0XgYG2cPiZbkvl93kTGaeDDhEXAMPLE -a protected-oasis-24054
|
|
18
|
+
Setting heroku config variables
|
|
19
|
+
Setting AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and restarting protected-oasis-24054... done, v21
|
|
20
|
+
|
|
21
|
+
AWS_ACCESS_KEY_ID: AKIAXZ6ODJLQQEXAMPLE
|
|
22
|
+
AWS_SECRET_ACCESS_KEY: sp4gmsuif0XgYG2cPiZbkvl93kTGaeDDhEXAMPLE
|
|
23
|
+
Old access key deleted: AKIAXZ6ODJLQSGGE27KK
|
|
24
|
+
$
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Add this line to your application's Gemfile:
|
|
29
|
+
|
|
30
|
+
gem "heroku-config"
|
|
31
|
+
|
|
32
|
+
And then execute:
|
|
33
|
+
|
|
34
|
+
bundle
|
|
35
|
+
|
|
36
|
+
Or install it yourself as:
|
|
37
|
+
|
|
38
|
+
gem install heroku-config
|
|
39
|
+
|
|
40
|
+
## Contributing
|
|
41
|
+
|
|
42
|
+
1. Fork it
|
|
43
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
44
|
+
3. Commit your changes (`git commit -am "Add some feature"`)
|
|
45
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
46
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
2
|
+
require "rspec/core/rake_task"
|
|
3
|
+
|
|
4
|
+
task default: :spec
|
|
5
|
+
|
|
6
|
+
RSpec::Core::RakeTask.new
|
|
7
|
+
|
|
8
|
+
require_relative "lib/heroku-config"
|
|
9
|
+
require "cli_markdown"
|
|
10
|
+
desc "Generates cli reference docs as markdown"
|
|
11
|
+
task :docs do
|
|
12
|
+
mkdir_p "docs/_includes"
|
|
13
|
+
CliMarkdown::Creator.create_all(cli_class: HerokuConfig::CLI, cli_name: "heroku-config")
|
|
14
|
+
end
|
data/exe/heroku-config
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Trap ^C
|
|
4
|
+
Signal.trap("INT") {
|
|
5
|
+
puts "\nCtrl-C detected. Exiting..."
|
|
6
|
+
sleep 0.1
|
|
7
|
+
exit
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
$:.unshift(File.expand_path("../../lib", __FILE__))
|
|
11
|
+
require "heroku-config"
|
|
12
|
+
require "heroku_config/cli"
|
|
13
|
+
|
|
14
|
+
HerokuConfig::CLI.start(ARGV)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require "heroku_config/version"
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "heroku-config"
|
|
8
|
+
spec.version = HerokuConfig::VERSION
|
|
9
|
+
spec.authors = ["Tung Nguyen"]
|
|
10
|
+
spec.email = ["tongueroo@gmail.com"]
|
|
11
|
+
spec.summary = "Heroku Config AWS Access Key Rotator"
|
|
12
|
+
spec.homepage = "https://github.com/tongueroo/heroku-config"
|
|
13
|
+
spec.license = "MIT"
|
|
14
|
+
|
|
15
|
+
spec.files = `git ls-files`.split($/)
|
|
16
|
+
spec.bindir = "exe"
|
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
spec.add_dependency "activesupport"
|
|
22
|
+
spec.add_dependency "aws-sdk-core" # for aws-sdk-sts
|
|
23
|
+
spec.add_dependency "aws-sdk-iam"
|
|
24
|
+
spec.add_dependency "memoist"
|
|
25
|
+
spec.add_dependency "rainbow"
|
|
26
|
+
spec.add_dependency "thor"
|
|
27
|
+
spec.add_dependency "zeitwerk"
|
|
28
|
+
|
|
29
|
+
spec.add_development_dependency "bundler"
|
|
30
|
+
spec.add_development_dependency "byebug"
|
|
31
|
+
spec.add_development_dependency "cli_markdown"
|
|
32
|
+
spec.add_development_dependency "rake"
|
|
33
|
+
spec.add_development_dependency "rspec"
|
|
34
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require_relative "heroku_config"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require "zeitwerk"
|
|
2
|
+
|
|
3
|
+
module HerokuConfig
|
|
4
|
+
class Autoloader
|
|
5
|
+
class Inflector < Zeitwerk::Inflector
|
|
6
|
+
def camelize(basename, _abspath)
|
|
7
|
+
map = { cli: "CLI", version: "VERSION" }
|
|
8
|
+
map[basename.to_sym] || super
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def setup
|
|
14
|
+
loader = Zeitwerk::Loader.new
|
|
15
|
+
loader.inflector = Inflector.new
|
|
16
|
+
loader.push_dir(File.dirname(__dir__)) # lib
|
|
17
|
+
loader.ignore("#{File.dirname(__dir__)}/heroku-config.rb")
|
|
18
|
+
loader.setup
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module HerokuConfig
|
|
2
|
+
class AwsKey < Base
|
|
3
|
+
include AwsServices
|
|
4
|
+
class MaxKeysError < StandardError; end
|
|
5
|
+
|
|
6
|
+
def initialize(options, access_key_id)
|
|
7
|
+
@options, @access_key_id = options, access_key_id
|
|
8
|
+
@app = options[:app]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def rotate
|
|
12
|
+
user_name = get_user_name
|
|
13
|
+
|
|
14
|
+
message = "Updating access key for user: #{user_name}"
|
|
15
|
+
message = "NOOP: #{message}" if ENV['HEROKU_CONFIG_TEST']
|
|
16
|
+
puts message.color(:green)
|
|
17
|
+
return false if @options[:noop]
|
|
18
|
+
|
|
19
|
+
check_max_keys_limit!(user_name)
|
|
20
|
+
new_key, new_secret = create_access_key(user_name)
|
|
21
|
+
wait_until_usable(new_key, new_secret)
|
|
22
|
+
|
|
23
|
+
update_heroku_config(new_key, new_secret)
|
|
24
|
+
delete_old_access_key(user_name)
|
|
25
|
+
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get_user_name
|
|
30
|
+
return "fakeuser" if @options[:noop]
|
|
31
|
+
|
|
32
|
+
resp = iam.get_access_key_last_used(
|
|
33
|
+
access_key_id: @access_key_id,
|
|
34
|
+
)
|
|
35
|
+
resp.user_name
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def wait_until_usable(key, secret)
|
|
39
|
+
delay, retries = 5, 0
|
|
40
|
+
begin
|
|
41
|
+
sts.get_caller_identity
|
|
42
|
+
true
|
|
43
|
+
rescue Aws::STS::Errors::InvalidClientTokenId => e
|
|
44
|
+
puts "#{e.class}: #{e.message}"
|
|
45
|
+
retries += 1
|
|
46
|
+
if retries <= 20
|
|
47
|
+
puts "New IAM key not usable yet. Delaying for #{delay} seconds and retrying..."
|
|
48
|
+
sleep delay
|
|
49
|
+
retry
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def delete_old_access_key(user_name)
|
|
55
|
+
resp = iam.list_access_keys(user_name: user_name)
|
|
56
|
+
access_keys = resp.access_key_metadata
|
|
57
|
+
# Important: Only delete if there are keys 2.
|
|
58
|
+
return if access_keys.size <= 1
|
|
59
|
+
|
|
60
|
+
old_key = access_keys.sort_by(&:create_date).first
|
|
61
|
+
iam.delete_access_key(user_name: user_name, access_key_id: old_key.access_key_id)
|
|
62
|
+
puts "Old access key deleted: #{old_key.access_key_id}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def update_heroku_config(new_key, new_secret)
|
|
66
|
+
out = config.set(
|
|
67
|
+
"AWS_ACCESS_KEY_ID" => new_key,
|
|
68
|
+
"AWS_SECRET_ACCESS_KEY" => new_secret,
|
|
69
|
+
)
|
|
70
|
+
puts "Setting heroku config variables"
|
|
71
|
+
puts out
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Returns:
|
|
75
|
+
#
|
|
76
|
+
# #<struct Aws::IAM::Types::AccessKey
|
|
77
|
+
# user_name="tung",
|
|
78
|
+
# access_key_id="AKIAXZ6ODJLQUU6O3FD2",
|
|
79
|
+
# status="Active",
|
|
80
|
+
# secret_access_key="8eEnLLdR7gQE9fkFiDVuemi3qPf3mBMXxEXAMPLE",
|
|
81
|
+
# create_date=2019-08-13 21:14:35 UTC>>
|
|
82
|
+
#
|
|
83
|
+
def create_access_key(user_name)
|
|
84
|
+
resp = iam.create_access_key(
|
|
85
|
+
user_name: user_name,
|
|
86
|
+
)
|
|
87
|
+
access_key = resp.access_key
|
|
88
|
+
key, secret = access_key.access_key_id, access_key.secret_access_key
|
|
89
|
+
puts "Created new access key: #{key}"
|
|
90
|
+
[key, secret]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
# Check if there are 2 keys, cannot rotate if there are 2 keys already.
|
|
95
|
+
# Raise error if there are 2 keys.
|
|
96
|
+
# Returns false if not at max limit
|
|
97
|
+
MAX_KEYS = 2
|
|
98
|
+
def check_max_keys_limit!(user_name)
|
|
99
|
+
resp = iam.list_access_keys(user_name: user_name)
|
|
100
|
+
return false if resp.access_key_metadata.size < MAX_KEYS # not at max limit
|
|
101
|
+
|
|
102
|
+
puts "ERROR: There are already 2 access keys for user: #{user_name.color(:green)}".color(:red)
|
|
103
|
+
raise MaxKeysError
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module HerokuConfig
|
|
2
|
+
class AwsRotate < Base
|
|
3
|
+
def initialize(options={})
|
|
4
|
+
@options = options
|
|
5
|
+
@app = options[:app]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def run
|
|
9
|
+
key_id = config.get("AWS_ACCESS_KEY_ID")
|
|
10
|
+
unless key_id
|
|
11
|
+
puts "WARN: No AWS_ACCESS_KEY_ID found for #{@app.color(:green)} app. Exiting."
|
|
12
|
+
exit 0
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
aws_key = AwsKey.new(@options, key_id)
|
|
16
|
+
aws_key.rotate
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module HerokuConfig
|
|
2
|
+
class CLI < Command
|
|
3
|
+
class_option :verbose, type: :boolean
|
|
4
|
+
class_option :noop, type: :boolean
|
|
5
|
+
|
|
6
|
+
desc "aws-rotate APP", "Say aws_rotate to APP"
|
|
7
|
+
long_desc Help.text(:aws_rotate)
|
|
8
|
+
def aws_rotate(app)
|
|
9
|
+
AwsRotate.new(options.merge(app: app)).run
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
desc "completion *PARAMS", "Prints words for auto-completion."
|
|
13
|
+
long_desc Help.text("completion")
|
|
14
|
+
def completion(*params)
|
|
15
|
+
Completer.new(CLI, *params).run
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
desc "completion_script", "Generates a script that can be eval to setup auto-completion."
|
|
19
|
+
long_desc Help.text("completion_script")
|
|
20
|
+
def completion_script
|
|
21
|
+
Completer::Script.generate
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc "version", "prints version"
|
|
25
|
+
def version
|
|
26
|
+
puts VERSION
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
require "thor"
|
|
2
|
+
|
|
3
|
+
# Override thor's long_desc identation behavior
|
|
4
|
+
# https://github.com/erikhuda/thor/issues/398
|
|
5
|
+
class Thor
|
|
6
|
+
module Shell
|
|
7
|
+
class Basic
|
|
8
|
+
def print_wrapped(message, options = {})
|
|
9
|
+
message = "\n#{message}" unless message[0] == "\n"
|
|
10
|
+
stdout.puts message
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module HerokuConfig
|
|
17
|
+
class Command < Thor
|
|
18
|
+
class << self
|
|
19
|
+
def dispatch(m, args, options, config)
|
|
20
|
+
# Allow calling for help via:
|
|
21
|
+
# heroku-config command help
|
|
22
|
+
# heroku-config command -h
|
|
23
|
+
# heroku-config command --help
|
|
24
|
+
# heroku-config command -D
|
|
25
|
+
#
|
|
26
|
+
# as well thor's normal way:
|
|
27
|
+
#
|
|
28
|
+
# heroku-config help command
|
|
29
|
+
help_flags = Thor::HELP_MAPPINGS + ["help"]
|
|
30
|
+
if args.length > 1 && !(args & help_flags).empty?
|
|
31
|
+
args -= help_flags
|
|
32
|
+
args.insert(-2, "help")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# heroku-config version
|
|
36
|
+
# heroku-config --version
|
|
37
|
+
# heroku-config -v
|
|
38
|
+
version_flags = ["--version", "-v"]
|
|
39
|
+
if args.length == 1 && !(args & version_flags).empty?
|
|
40
|
+
args = ["version"]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
super
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Override command_help to include the description at the top of the
|
|
47
|
+
# long_description.
|
|
48
|
+
def command_help(shell, command_name)
|
|
49
|
+
meth = normalize_command_name(command_name)
|
|
50
|
+
command = all_commands[meth]
|
|
51
|
+
alter_command_description(command)
|
|
52
|
+
super
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def alter_command_description(command)
|
|
56
|
+
return unless command
|
|
57
|
+
|
|
58
|
+
# Add description to beginning of long_description
|
|
59
|
+
long_desc = if command.long_description
|
|
60
|
+
"#{command.description}\n\n#{command.long_description}"
|
|
61
|
+
else
|
|
62
|
+
command.description
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# add reference url to end of the long_description
|
|
66
|
+
unless website.empty?
|
|
67
|
+
full_command = [command.ancestor_name, command.name].compact.join('-')
|
|
68
|
+
url = "#{website}/reference/heroku-config-#{full_command}"
|
|
69
|
+
long_desc += "\n\nHelp also available at: #{url}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
command.long_description = long_desc
|
|
73
|
+
end
|
|
74
|
+
private :alter_command_description
|
|
75
|
+
|
|
76
|
+
# meant to be overriden
|
|
77
|
+
def website
|
|
78
|
+
""
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
_heroku-config() {
|
|
2
|
+
COMPREPLY=()
|
|
3
|
+
local word="${COMP_WORDS[COMP_CWORD]}"
|
|
4
|
+
local words=("${COMP_WORDS[@]}")
|
|
5
|
+
unset words[0]
|
|
6
|
+
local completion=$(heroku-config completion ${words[@]})
|
|
7
|
+
COMPREPLY=( $(compgen -W "$completion" -- "$word") )
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
complete -F _heroku-config heroku-config
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
Code Explanation:
|
|
3
|
+
|
|
4
|
+
There are 3 types of things to auto-complete:
|
|
5
|
+
|
|
6
|
+
1. command: the command itself
|
|
7
|
+
2. parameters: command parameters.
|
|
8
|
+
3. options: command options
|
|
9
|
+
|
|
10
|
+
Here's an example:
|
|
11
|
+
|
|
12
|
+
mycli hello name --from me
|
|
13
|
+
|
|
14
|
+
* command: hello
|
|
15
|
+
* parameters: name
|
|
16
|
+
* option: --from
|
|
17
|
+
|
|
18
|
+
When command parameters are done processing, the remaining completion words will be options. We can tell that the command params are completed based on the method arity.
|
|
19
|
+
|
|
20
|
+
## Arity
|
|
21
|
+
|
|
22
|
+
For example, say you had a method for a CLI command with the following form:
|
|
23
|
+
|
|
24
|
+
ufo scale service count --cluster development
|
|
25
|
+
|
|
26
|
+
It's equivalent ruby method:
|
|
27
|
+
|
|
28
|
+
scale(service, count) = has an arity of 2
|
|
29
|
+
|
|
30
|
+
So typing:
|
|
31
|
+
|
|
32
|
+
ufo scale service count [TAB] # there are 3 parameters including the "scale" command according to Thor's CLI processing.
|
|
33
|
+
|
|
34
|
+
So the completion should only show options, something like this:
|
|
35
|
+
|
|
36
|
+
--noop --verbose --cluster
|
|
37
|
+
|
|
38
|
+
## Splat Arguments
|
|
39
|
+
|
|
40
|
+
When the ruby method has a splat argument, it's arity is negative. Here are some example methods and their arities.
|
|
41
|
+
|
|
42
|
+
ship(service) = 1
|
|
43
|
+
scale(service, count) = 2
|
|
44
|
+
ships(*services) = -1
|
|
45
|
+
foo(example, *rest) = -2
|
|
46
|
+
|
|
47
|
+
Fortunately, negative and positive arity values are processed the same way. So we take simply take the absolute value of the arity and process it the same.
|
|
48
|
+
|
|
49
|
+
Here are some test cases, hit TAB after typing the command:
|
|
50
|
+
|
|
51
|
+
heroku-config completion
|
|
52
|
+
heroku-config completion hello
|
|
53
|
+
heroku-config completion hello name
|
|
54
|
+
heroku-config completion hello name --
|
|
55
|
+
heroku-config completion hello name --noop
|
|
56
|
+
|
|
57
|
+
heroku-config completion
|
|
58
|
+
heroku-config completion sub:goodbye
|
|
59
|
+
heroku-config completion sub:goodbye name
|
|
60
|
+
|
|
61
|
+
## Subcommands and Thor::Group Registered Commands
|
|
62
|
+
|
|
63
|
+
Sometimes the commands are not simple thor commands but are subcommands or Thor::Group commands. A good specific example is the ufo tool.
|
|
64
|
+
|
|
65
|
+
* regular command: ufo ship
|
|
66
|
+
* subcommand: ufo docker
|
|
67
|
+
* Thor::Group command: ufo init
|
|
68
|
+
|
|
69
|
+
Auto-completion accounts for each of these type of commands.
|
|
70
|
+
=end
|
|
71
|
+
module HerokuConfig
|
|
72
|
+
class Completer
|
|
73
|
+
def initialize(command_class, *params)
|
|
74
|
+
@params = params
|
|
75
|
+
@current_command = @params[0]
|
|
76
|
+
@command_class = command_class # CLI initiall
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def run
|
|
80
|
+
if subcommand?(@current_command)
|
|
81
|
+
subcommand_class = @command_class.subcommand_classes[@current_command]
|
|
82
|
+
@params.shift # destructive
|
|
83
|
+
Completer.new(subcommand_class, *@params).run # recursively use subcommand
|
|
84
|
+
return
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# full command has been found!
|
|
88
|
+
unless found?(@current_command)
|
|
89
|
+
puts all_commands
|
|
90
|
+
return
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# will only get to here if command aws found (above)
|
|
94
|
+
arity = @command_class.instance_method(@current_command).arity.abs
|
|
95
|
+
if @params.size > arity or thor_group_command?
|
|
96
|
+
puts options_completion
|
|
97
|
+
else
|
|
98
|
+
puts params_completion
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def subcommand?(command)
|
|
103
|
+
@command_class.subcommands.include?(command)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# hacky way to detect that command is a registered Thor::Group command
|
|
107
|
+
def thor_group_command?
|
|
108
|
+
command_params(raw=true) == [[:rest, :args]]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def found?(command)
|
|
112
|
+
public_methods = @command_class.public_instance_methods(false)
|
|
113
|
+
command && public_methods.include?(command.to_sym)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# all top-level commands
|
|
117
|
+
def all_commands
|
|
118
|
+
commands = @command_class.all_commands.reject do |k,v|
|
|
119
|
+
v.is_a?(Thor::HiddenCommand)
|
|
120
|
+
end
|
|
121
|
+
commands.keys
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def command_params(raw=false)
|
|
125
|
+
params = @command_class.instance_method(@current_command).parameters
|
|
126
|
+
# Example:
|
|
127
|
+
# >> Sub.instance_method(:goodbye).parameters
|
|
128
|
+
# => [[:req, :name]]
|
|
129
|
+
# >>
|
|
130
|
+
raw ? params : params.map!(&:last)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def params_completion
|
|
134
|
+
offset = @params.size - 1
|
|
135
|
+
offset_params = command_params[offset..-1]
|
|
136
|
+
command_params[offset..-1].first
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def options_completion
|
|
140
|
+
used = ARGV.select { |a| a.include?('--') } # so we can remove used options
|
|
141
|
+
|
|
142
|
+
method_options = @command_class.all_commands[@current_command].options.keys
|
|
143
|
+
class_options = @command_class.class_options.keys
|
|
144
|
+
|
|
145
|
+
all_options = method_options + class_options + ['help']
|
|
146
|
+
|
|
147
|
+
all_options.map! { |o| "--#{o.to_s.gsub('_','-')}" }
|
|
148
|
+
filtered_options = all_options - used
|
|
149
|
+
filtered_options.uniq
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Useful for debugging. Using puts messes up completion.
|
|
153
|
+
def log(msg)
|
|
154
|
+
File.open("/tmp/complete.log", "a") do |file|
|
|
155
|
+
file.puts(msg)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'open3'
|
|
2
|
+
|
|
3
|
+
module HerokuConfig
|
|
4
|
+
class Config
|
|
5
|
+
def initialize(app)
|
|
6
|
+
@app = app
|
|
7
|
+
check_heroku_cli_installed!
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def get(name)
|
|
11
|
+
return "fakevalue" if ENV['HEROKU_CONFIG_TEST']
|
|
12
|
+
sh "heroku config:get #{name} -a #{@app}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def set(*params)
|
|
16
|
+
case params.size
|
|
17
|
+
when 1 # Hash
|
|
18
|
+
set_many(params.first)
|
|
19
|
+
when 2 # 2 Strings
|
|
20
|
+
set_one(name, value)
|
|
21
|
+
else
|
|
22
|
+
raise "ERROR: #{params.class} is a class that is not supported"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def set_one(name, value)
|
|
27
|
+
sh "heroku config:set #{name} #{value} -a #{@app}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Example:
|
|
31
|
+
#
|
|
32
|
+
# set(a: 1, b: 2)
|
|
33
|
+
# =>
|
|
34
|
+
# heroku config:set a=1 b=2 -a APP
|
|
35
|
+
#
|
|
36
|
+
def set_many(hash)
|
|
37
|
+
args = hash.map { |k,v| "#{k}=#{v}" }.join(' ')
|
|
38
|
+
sh "heroku config:set #{args} -a #{@app}", include_stderr: true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
def sh(command, include_stderr: false)
|
|
43
|
+
puts "=> #{command}"
|
|
44
|
+
stdout, stderr, status = Open3.capture3(command)
|
|
45
|
+
|
|
46
|
+
out = stdout.strip
|
|
47
|
+
unless status.success?
|
|
48
|
+
puts "ERROR: #{stderr}".color(:red)
|
|
49
|
+
if stderr.empty?
|
|
50
|
+
puts "STDOUT: #{stdout}"
|
|
51
|
+
end
|
|
52
|
+
exit 1
|
|
53
|
+
end
|
|
54
|
+
if include_stderr
|
|
55
|
+
stderr + "\n" + out
|
|
56
|
+
else
|
|
57
|
+
out
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def check_heroku_cli_installed!
|
|
62
|
+
return if heroku_cli_installed?
|
|
63
|
+
|
|
64
|
+
puts "The heroku cli is not installed. Please install the heroku cli: https://devcenter.heroku.com/articles/heroku-cli"
|
|
65
|
+
exit 1
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def heroku_cli_installed?
|
|
69
|
+
system("type heroku > /dev/null 2>&1")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
## Examples
|
|
2
|
+
|
|
3
|
+
heroku-config aws-rotate APP
|
|
4
|
+
|
|
5
|
+
## Example with Output
|
|
6
|
+
|
|
7
|
+
$ heroku-config aws-rotate protected-oasis-24054
|
|
8
|
+
=> heroku config:get AWS_ACCESS_KEY_ID -a protected-oasis-24054
|
|
9
|
+
Updating access key for user: bob
|
|
10
|
+
Created new access key: AKIAXZ6ODJLQQEXAMPLE
|
|
11
|
+
=> heroku config:set AWS_ACCESS_KEY_ID=AKIAXZ6ODJLQQEXAMPLE AWS_SECRET_ACCESS_KEY=sp4gmsuif0XgYG2cPiZbkvl93kTGaeDDhEXAMPLE -a protected-oasis-24054
|
|
12
|
+
Setting heroku config variables
|
|
13
|
+
Setting AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and restarting protected-oasis-24054... done, v21
|
|
14
|
+
|
|
15
|
+
AWS_ACCESS_KEY_ID: AKIAXZ6ODJLQQEXAMPLE
|
|
16
|
+
AWS_SECRET_ACCESS_KEY: sp4gmsuif0XgYG2cPiZbkvl93kTGaeDDhEXAMPLE
|
|
17
|
+
Old access key deleted: AKIAXZ6ODJLQSGGE27KK
|
|
18
|
+
$
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## Examples
|
|
2
|
+
|
|
3
|
+
heroku-config completion
|
|
4
|
+
|
|
5
|
+
Prints words for TAB auto-completion.
|
|
6
|
+
|
|
7
|
+
heroku-config completion
|
|
8
|
+
heroku-config completion hello
|
|
9
|
+
heroku-config completion hello name
|
|
10
|
+
|
|
11
|
+
To enable, TAB auto-completion add the following to your profile:
|
|
12
|
+
|
|
13
|
+
eval $(heroku-config completion_script)
|
|
14
|
+
|
|
15
|
+
Auto-completion example usage:
|
|
16
|
+
|
|
17
|
+
heroku-config [TAB]
|
|
18
|
+
heroku-config hello [TAB]
|
|
19
|
+
heroku-config hello name [TAB]
|
|
20
|
+
heroku-config hello name --[TAB]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
$:.unshift(File.expand_path("../", __FILE__))
|
|
2
|
+
require "heroku_config/version"
|
|
3
|
+
require "memoist"
|
|
4
|
+
require "rainbow/ext/string"
|
|
5
|
+
|
|
6
|
+
require "heroku_config/autoloader"
|
|
7
|
+
HerokuConfig::Autoloader.setup
|
|
8
|
+
|
|
9
|
+
module HerokuConfig
|
|
10
|
+
class Error < StandardError; end
|
|
11
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
describe HerokuConfig::CLI do
|
|
2
|
+
before(:all) do
|
|
3
|
+
@args = "APP --noop"
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
describe "heroku-config" do
|
|
7
|
+
it "aws-rotate" do
|
|
8
|
+
out = execute("exe/heroku-config aws-rotate #{@args}")
|
|
9
|
+
puts out
|
|
10
|
+
expect(out).to include("NOOP: Updating access key for user: fakeuser")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
ENV["HEROKU_CONFIG_TEST"] = "1"
|
|
2
|
+
|
|
3
|
+
# CodeClimate test coverage: https://docs.codeclimate.com/docs/configuring-test-coverage
|
|
4
|
+
# require 'simplecov'
|
|
5
|
+
# SimpleCov.start
|
|
6
|
+
|
|
7
|
+
require "pp"
|
|
8
|
+
require "byebug"
|
|
9
|
+
root = File.expand_path("../", File.dirname(__FILE__))
|
|
10
|
+
require "#{root}/lib/heroku-config"
|
|
11
|
+
|
|
12
|
+
module Helper
|
|
13
|
+
def execute(cmd)
|
|
14
|
+
puts "Running: #{cmd}" if show_command?
|
|
15
|
+
out = `#{cmd}`
|
|
16
|
+
puts out if show_command?
|
|
17
|
+
out
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Added SHOW_COMMAND because DEBUG is also used by other libraries like
|
|
21
|
+
# bundler and it shows its internal debugging logging also.
|
|
22
|
+
def show_command?
|
|
23
|
+
ENV['DEBUG'] || ENV['SHOW_COMMAND']
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
RSpec.configure do |c|
|
|
28
|
+
c.include Helper
|
|
29
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: heroku-config
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tung Nguyen
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2019-11-16 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activesupport
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
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: aws-sdk-core
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: aws-sdk-iam
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
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: memoist
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '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'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rainbow
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: thor
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: zeitwerk
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :runtime
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: bundler
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: byebug
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: cli_markdown
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - ">="
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '0'
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - ">="
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '0'
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: rake
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - ">="
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '0'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - ">="
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '0'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: rspec
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - ">="
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: '0'
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - ">="
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: '0'
|
|
181
|
+
description:
|
|
182
|
+
email:
|
|
183
|
+
- tongueroo@gmail.com
|
|
184
|
+
executables:
|
|
185
|
+
- heroku-config
|
|
186
|
+
extensions: []
|
|
187
|
+
extra_rdoc_files: []
|
|
188
|
+
files:
|
|
189
|
+
- ".gitignore"
|
|
190
|
+
- ".rspec"
|
|
191
|
+
- CHANGELOG.md
|
|
192
|
+
- Gemfile
|
|
193
|
+
- Gemfile.lock
|
|
194
|
+
- Guardfile
|
|
195
|
+
- LICENSE.txt
|
|
196
|
+
- README.md
|
|
197
|
+
- Rakefile
|
|
198
|
+
- exe/heroku-config
|
|
199
|
+
- heroku-config.gemspec
|
|
200
|
+
- lib/heroku-config.rb
|
|
201
|
+
- lib/heroku_config.rb
|
|
202
|
+
- lib/heroku_config/autoloader.rb
|
|
203
|
+
- lib/heroku_config/aws_key.rb
|
|
204
|
+
- lib/heroku_config/aws_rotate.rb
|
|
205
|
+
- lib/heroku_config/aws_services.rb
|
|
206
|
+
- lib/heroku_config/base.rb
|
|
207
|
+
- lib/heroku_config/cli.rb
|
|
208
|
+
- lib/heroku_config/command.rb
|
|
209
|
+
- lib/heroku_config/completer.rb
|
|
210
|
+
- lib/heroku_config/completer/script.rb
|
|
211
|
+
- lib/heroku_config/completer/script.sh
|
|
212
|
+
- lib/heroku_config/config.rb
|
|
213
|
+
- lib/heroku_config/help.rb
|
|
214
|
+
- lib/heroku_config/help/aws_rotate.md
|
|
215
|
+
- lib/heroku_config/help/completion.md
|
|
216
|
+
- lib/heroku_config/help/completion_script.md
|
|
217
|
+
- lib/heroku_config/version.rb
|
|
218
|
+
- spec/lib/cli_spec.rb
|
|
219
|
+
- spec/spec_helper.rb
|
|
220
|
+
homepage: https://github.com/tongueroo/heroku-config
|
|
221
|
+
licenses:
|
|
222
|
+
- MIT
|
|
223
|
+
metadata: {}
|
|
224
|
+
post_install_message:
|
|
225
|
+
rdoc_options: []
|
|
226
|
+
require_paths:
|
|
227
|
+
- lib
|
|
228
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
229
|
+
requirements:
|
|
230
|
+
- - ">="
|
|
231
|
+
- !ruby/object:Gem::Version
|
|
232
|
+
version: '0'
|
|
233
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
234
|
+
requirements:
|
|
235
|
+
- - ">="
|
|
236
|
+
- !ruby/object:Gem::Version
|
|
237
|
+
version: '0'
|
|
238
|
+
requirements: []
|
|
239
|
+
rubygems_version: 3.0.6
|
|
240
|
+
signing_key:
|
|
241
|
+
specification_version: 4
|
|
242
|
+
summary: Heroku Config AWS Access Key Rotator
|
|
243
|
+
test_files:
|
|
244
|
+
- spec/lib/cli_spec.rb
|
|
245
|
+
- spec/spec_helper.rb
|