aboutsummaryrefslogtreecommitdiffstats
path: root/railties
diff options
context:
space:
mode:
authorRafael França <rafael@franca.dev>2019-07-26 14:18:14 -0400
committerGitHub <noreply@github.com>2019-07-26 14:18:14 -0400
commitea4305109e6dd2833e6e81fe8e70e0b6951a3c9d (patch)
tree39c0a5e07cf456b24bc4bae011c97b62e1f2bbca /railties
parent0643dc44d5a61ad8a3bedb8bdf9b0f0840035607 (diff)
parent5a4acf7ac42c44a17a645e8d3682fd0cc145428d (diff)
downloadrails-ea4305109e6dd2833e6e81fe8e70e0b6951a3c9d.tar.gz
rails-ea4305109e6dd2833e6e81fe8e70e0b6951a3c9d.tar.bz2
rails-ea4305109e6dd2833e6e81fe8e70e0b6951a3c9d.zip
Merge pull request #36777 from Edouard-chin/ec-git-pretty-credentials
Prettify diff generated by git for encrypted file:
Diffstat (limited to 'railties')
-rw-r--r--railties/lib/rails/command/helpers/pretty_credentials.rb55
-rw-r--r--railties/lib/rails/commands/credentials/credentials_command.rb34
-rw-r--r--railties/test/commands/credentials_test.rb101
-rw-r--r--railties/test/isolation/abstract_unit.rb4
4 files changed, 185 insertions, 9 deletions
diff --git a/railties/lib/rails/command/helpers/pretty_credentials.rb b/railties/lib/rails/command/helpers/pretty_credentials.rb
new file mode 100644
index 0000000000..873ed0e825
--- /dev/null
+++ b/railties/lib/rails/command/helpers/pretty_credentials.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require "fileutils"
+
+module Rails
+ module Command
+ module Helpers
+ module PrettyCredentials
+ Error = Class.new(StandardError)
+
+ def opt_in_pretty_credentials
+ unless already_answered? || already_opted_in?
+ answer = yes?("Would you like to make the credentials diff from git more readable in the future? [Y/n]")
+ end
+
+ opt_in! if answer
+ FileUtils.touch(tracker) unless answer.nil?
+ rescue Error
+ say("Couldn't setup git to prettify the credentials diff")
+ end
+
+ private
+ def already_answered?
+ tracker.exist?
+ end
+
+ def already_opted_in?
+ system_call("git config --get 'diff.rails_credentials.textconv'", accepted_codes: [0, 1])
+ end
+
+ def opt_in!
+ system_call("git config diff.rails_credentials.textconv 'bin/rails credentials:show'", accepted_codes: [0])
+
+ git_attributes = Rails.root.join(".gitattributes")
+ File.open(git_attributes, "a+") do |file|
+ file.write(<<~EOM)
+ config/credentials/*.yml.enc diff=rails_credentials
+ config/credentials.yml.enc diff=rails_credentials
+ EOM
+ end
+ end
+
+ def tracker
+ Rails.root.join("tmp", "rails_pretty_credentials")
+ end
+
+ def system_call(command_line, accepted_codes:)
+ result = system(command_line)
+ raise(Error) if accepted_codes.exclude?($?.exitstatus)
+ result
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/credentials/credentials_command.rb b/railties/lib/rails/commands/credentials/credentials_command.rb
index e23a1b3008..772e105007 100644
--- a/railties/lib/rails/commands/credentials/credentials_command.rb
+++ b/railties/lib/rails/commands/credentials/credentials_command.rb
@@ -2,12 +2,15 @@
require "active_support"
require "rails/command/helpers/editor"
+require "rails/command/helpers/pretty_credentials"
require "rails/command/environment_argument"
+require "pathname"
module Rails
module Command
class CredentialsCommand < Rails::Command::Base # :nodoc:
include Helpers::Editor
+ include Helpers::PrettyCredentials
include EnvironmentArgument
self.environment_desc = "Uses credentials from config/credentials/:environment.yml.enc encrypted by config/credentials/:environment.key key"
@@ -34,20 +37,29 @@ module Rails
end
say "File encrypted and saved."
+ opt_in_pretty_credentials
rescue ActiveSupport::MessageEncryptor::InvalidMessage
say "Couldn't decrypt #{content_path}. Perhaps you passed the wrong key?"
end
- def show
- extract_environment_option_from_argument(default_environment: nil)
+ def show(git_textconv_path = nil)
+ if git_textconv_path
+ default_environment = extract_environment_from_path(git_textconv_path)
+ fallback_message = File.read(git_textconv_path)
+ end
+
+ extract_environment_option_from_argument(default_environment: default_environment)
require_application!
- say credentials.read.presence || missing_credentials_message
+ say credentials(git_textconv_path).read.presence || fallback_message || missing_credentials_message
+ rescue => e
+ raise(e) unless git_textconv_path
+ fallback_message
end
private
- def credentials
- Rails.application.encrypted(content_path, key_path: key_path)
+ def credentials(content = nil)
+ Rails.application.encrypted(content || content_path, key_path: key_path)
end
def ensure_encryption_key_has_been_added
@@ -77,7 +89,6 @@ module Rails
end
end
-
def content_path
options[:environment] ? "config/credentials/#{options[:environment]}.yml.enc" : "config/credentials.yml.enc"
end
@@ -86,6 +97,17 @@ module Rails
options[:environment] ? "config/credentials/#{options[:environment]}.key" : "config/master.key"
end
+ def extract_environment_from_path(path)
+ regex = %r{
+ ([A-Za-z0-9]+) # match the environment
+ (?<!credentials) # don't match if file contains the word "credentials"
+ # in such case, the environment should be the default one
+ \.yml\.enc # look for `.yml.enc` file extension
+ }x
+ path.match(regex)
+
+ Regexp.last_match(1)
+ end
def encryption_key_file_generator
require "rails/generators"
diff --git a/railties/test/commands/credentials_test.rb b/railties/test/commands/credentials_test.rb
index 0ee36081c0..562e2ec382 100644
--- a/railties/test/commands/credentials_test.rb
+++ b/railties/test/commands/credentials_test.rb
@@ -4,6 +4,7 @@ require "isolation/abstract_unit"
require "env_helpers"
require "rails/command"
require "rails/commands/credentials/credentials_command"
+require "fileutils"
class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation, EnvHelpers
@@ -88,10 +89,107 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase
assert_match(/secret_key_base/, output)
end
+ test "edit ask the user to opt in to pretty credentials" do
+ assert_match(/Would you like to make the credentials diff from git/, run_edit_command)
+ end
+
+ test "edit doesn't ask the user to opt in to pretty credentials when alreasy asked" do
+ app_file("tmp/rails_pretty_credentials", "")
+
+ assert_no_match(/Would you like to make the credentials diff from git/, run_edit_command)
+ end
+
+ test "edit doesn't ask the user to opt in when user already opted in" do
+ content = <<~EOM
+ [diff "rails_credentials"]
+ textconv = bin/rails credentials:show
+ EOM
+ app_file(".git/config", content)
+
+ assert_no_match(/Would you like to make the credentials diff from git/, run_edit_command)
+ end
+
+ test "edit ask the user to opt in to pretty credentials, user accepts" do
+ file = File.open("foo", "w")
+ file.write("y")
+ file.rewind
+
+ run_edit_command(stdin: file.path)
+
+ git_attributes = app_path(".gitattributes")
+ expected = <<~EOM
+ config/credentials/*.yml.enc diff=rails_credentials
+ config/credentials.yml.enc diff=rails_credentials
+ EOM
+ assert(File.exist?(git_attributes))
+ assert_equal(expected, File.read(git_attributes))
+ Dir.chdir(app_path) do
+ assert_equal("bin/rails credentials:show\n", `git config --get 'diff.rails_credentials.textconv'`)
+ end
+ ensure
+ File.delete(file)
+ end
+
+ test "edit ask the user to opt in to pretty credentials, user refuses" do
+ file = File.open("foo", "w")
+ file.write("n")
+ file.rewind
+
+ run_edit_command(stdin: file.path)
+
+ git_attributes = app_path(".gitattributes")
+ assert_not(File.exist?(git_attributes))
+ ensure
+ File.delete(file)
+ end
+
test "show credentials" do
assert_match(/access_key_id: 123/, run_show_command)
end
+ test "show command when argument is provided (from git diff left file)" do
+ run_edit_command(environment: "development")
+
+ assert_match(/access_key_id: 123/, run_show_command("config/credentials/development.yml.enc"))
+ end
+
+ test "show command when argument is provided (from git diff right file)" do
+ run_edit_command(environment: "development")
+
+ dir = Dir.mktmpdir
+ file_path = File.join(dir, "KnAM4a_development.yml.enc")
+ file_content = File.read(app_path("config", "credentials", "development.yml.enc"))
+ File.write(file_path, file_content)
+
+ assert_match(/access_key_id: 123/, run_show_command(file_path))
+ ensure
+ FileUtils.rm_rf(dir)
+ end
+
+ test "show command when argument is provided (git diff) and filename is the master credentials" do
+ assert_match(/access_key_id: 123/, run_show_command("config/credentials.yml.enc"))
+ end
+
+ test "show command when argument is provided (git diff) and master key is not available" do
+ remove_file "config/master.key"
+
+ raw_content = File.read(app_path("config", "credentials.yml.enc"))
+ assert_match(raw_content, run_show_command("config/credentials.yml.enc"))
+ end
+
+ test "show command when argument is provided (git diff) return the raw encrypted content in an error occurs" do
+ run_edit_command(environment: "development")
+
+ dir = Dir.mktmpdir
+ file_path = File.join(dir, "20190807development.yml.enc")
+ file_content = File.read(app_path("config", "credentials", "development.yml.enc"))
+ File.write(file_path, file_content)
+
+ assert_match(file_content, run_show_command(file_path))
+ ensure
+ FileUtils.rm_rf(dir)
+ end
+
test "show command raises error when require_master_key is specified and key does not exist" do
remove_file "config/master.key"
add_to_config "config.require_master_key = true"
@@ -128,8 +226,9 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase
end
end
- def run_show_command(environment: nil, **options)
+ def run_show_command(path = nil, environment: nil, **options)
args = environment ? ["--environment", environment] : []
+ args.unshift(path)
rails "credentials:show", args, **options
end
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 0fe62df8ba..6077ba3ee7 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -301,7 +301,7 @@ module TestHelpers
# stderr:: true to pass STDERR output straight to the "real" STDERR.
# By default, the STDERR and STDOUT of the process will be
# combined in the returned string.
- def rails(*args, allow_failure: false, stderr: false)
+ def rails(*args, allow_failure: false, stderr: false, stdin: File::NULL)
args = args.flatten
fork = true
@@ -328,7 +328,7 @@ module TestHelpers
out_read.close
err_read.close if err_read
- $stdin.reopen(File::NULL, "r")
+ $stdin.reopen(stdin, "r")
$stdout.reopen(out_write)
$stderr.reopen(err_write)