From 0a64ac067bd018c768461890ad9bb1748486ff6f Mon Sep 17 00:00:00 2001 From: Molly Struve Date: Fri, 14 Aug 2020 12:20:47 -0500 Subject: [PATCH] [deploy] Remove ENVied Gem, Use dotenv Take 2 (#9767) --- .gitignore | 1 + .gitpod.yml | 2 +- .travis.yml | 3 +- Envfile | 177 ------------------- Gemfile | 2 +- Gemfile.lock | 8 +- app/views/admin/configs/show.html.erb | 12 +- bin/setup | 4 + config/.env_sample | 156 ++++++++++++++++ config/application.rb | 2 + config/environments/production.rb | 11 +- config/initializers/0_application_config.rb | 14 +- config/initializers/aws_sdk.rb | 2 +- config/initializers/carrierwave.rb | 6 +- config/initializers/pusher.rb | 20 ++- config/initializers/slack_notifier.rb | 10 +- config/sample_application.yml | 164 +++++++++++++++-- docs/backend/configuration.md | 11 +- docs/faqs.md | 6 +- docs/getting-started/config-env.md | 24 +-- docs/installation/containers.md | 11 +- docs/installation/linux.md | 11 +- docs/installation/mac.md | 11 +- docs/installation/postgresql.md | 10 +- docs/installation/vault.md | 8 +- docs/installation/windows.md | 14 +- docs/troubleshooting.md | 6 +- lib/tasks/temporary/env_variables.rake | 29 +++ spec/initializers/application_config_spec.rb | 6 + vendor/cache/dotenv-2.7.6.gem | Bin 0 -> 12800 bytes vendor/cache/dotenv-rails-2.7.6.gem | Bin 0 -> 10240 bytes vendor/cache/envied-0.9.3.gem | Bin 23552 -> 0 bytes 32 files changed, 450 insertions(+), 291 deletions(-) delete mode 100644 Envfile create mode 100644 config/.env_sample create mode 100644 lib/tasks/temporary/env_variables.rake create mode 100644 vendor/cache/dotenv-2.7.6.gem create mode 100644 vendor/cache/dotenv-rails-2.7.6.gem delete mode 100644 vendor/cache/envied-0.9.3.gem diff --git a/.gitignore b/.gitignore index 29cb72ad9..91226854d 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ latest.dump /node_modules yarn-debug.log* .yarn-integrity +.env # Ignore storybook static site generation app/javascript/storybook-static/ diff --git a/.gitpod.yml b/.gitpod.yml index 7365367ba..6376f0fcb 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -21,7 +21,7 @@ tasks: redis-server & /home/gitpod/elasticsearch-7.5.2/bin/elasticsearch & init: > - cp config/sample_application.yml config/application.yml && + cp config/.env_sample .env && gem install solargraph && bin/setup command: > diff --git a/.travis.yml b/.travis.yml index 4324b254b..1caee957b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,8 +24,6 @@ env: # Dummy values needed to verify the app boots via "rails runner" - APP_PROTOCOL=http:// - APP_DOMAIN=localhost:3000 - - SENDGRID_USERNAME_ACCEL=dummy - - SENDGRID_PASSWORD_ACCEL=dummy - HEROKU_APP_URL=practicaldev.herokuapp.com - SECRET_KEY_BASE=dummydummydummy - GITHUB_KEY=dummy @@ -45,6 +43,7 @@ install: true script: - date --rfc-3339=seconds - nvm install + - cp config/.env_sample .env - pkill -9 -f elasticsearch || true - curl -s -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.5.2-amd64.deb - sudo dpkg -i --force-confnew elasticsearch-7.5.2-amd64.deb diff --git a/Envfile b/Envfile deleted file mode 100644 index 92bc254cd..000000000 --- a/Envfile +++ /dev/null @@ -1,177 +0,0 @@ -# Most of the things are optional. If you need to use a -# custom key, please create an application.yml for it - -enable_defaults! { ENV["RACK_ENV"] != "production" } - -################################################ -############## Basic configuration ############# -################################################ - -# App domain + Protocol setting for development -variable :APP_DOMAIN, :String, default: "localhost:3000" -variable :APP_PROTOCOL, :String, default: "http://" - -# Community Related Variables -variable :COMMUNITY_COPYRIGHT_START_YEAR, :String, default: "2016" -variable :COMMUNITY_NAME, :String, default: "DEV(local)" - -# Email related variables -variable :DEFAULT_EMAIL, :String, default: "yo@dev.to" - -# Service timeout length -variable :RACK_TIMEOUT_WAIT_TIMEOUT, :Integer, default: 100_000 -variable :RACK_TIMEOUT_SERVICE_TIMEOUT, :Integer, default: 100_000 - -# Heroku release slug used to bust caches on deploys -variable :HEROKU_SLUG_COMMIT, :String, default: "Optional" - -# Redis caching storage -variable :REDIS_URL, :String, default: "redis://localhost:6379" - -# Redis sessions storage -variable :REDIS_SESSIONS_URL, :String, default: "redis://localhost:6379" -variable :SESSION_KEY, :String, default: "_Dev_Community_Session" -variable :SESSION_EXPIRY_SECONDS, :Integer, default: 1209600 # two weeks in seconds - -# Redis Sidekiq storage -variable :REDIS_SIDEKIQ_URL, :String, default: "redis://localhost:6379" - -# Elasticsearch -variable :ELASTICSEARCH_URL, :String, default: "http://localhost:9200" - -# SSL config -variable :FORCE_SSL_IN_RAILS, :String, default: "not applicable in dev" - -################################################ -############## 3rd Party Services ############## -################################################ - -# It's best if you have the following configured -# for more development experience - -# GitHub -# (https://developer.github.com/v3/guides/basics-of-authentication/) -variable :GITHUB_KEY, :String, default: "" -variable :GITHUB_SECRET, :String, default: "" - -# Twitter -# (https://developer.twitter.com/en/docs/basics/authentication/oauth-2-0/application-only) -variable :TWITTER_KEY, :String, default: "" -variable :TWITTER_SECRET, :String, default: "" - -################################################ -######### Optional 3rd Party Services ########## -################################################ - -# Honeybadger for error tracking -# (https://docs.honeybadger.io/lib/ruby/getting-started/introduction.html) -variable :HONEYBADGER_API_KEY, :String, default: "Optional" -variable :HONEYBADGER_JS_API_KEY, :String, default: "Optional" - -# AWS for images storages -variable :AWS_ID, :String, default: "Optional" -variable :AWS_SECRET, :String, default: "Optional" -variable :AWS_BUCKET_NAME, :String, default: "Optional" -variable :AWS_UPLOAD_REGION, :String, default: "" - -# AWS SDK -# (https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html) -variable :AWS_DEFAULT_REGION, :String, default: "us-east-1" -variable :AWS_SDK_KEY, :String, default: "Optional" -variable :AWS_SDK_SECRET, :String, default: "Optional" - -# AWS for video storage -variable :AWS_S3_INPUT_BUCKET, :String, default: "Optional" -variable :AWS_S3_VIDEO_ID, :String, default: "Optional" -variable :AWS_S3_VIDEO_KEY, :String, default: "Optional" - -# Buffer for sending to buffer -# (https://buffer.com/developers/api) -variable :BUFFER_ACCESS_TOKEN, :String, default: "Optional" -variable :BUFFER_FACEBOOK_ID, :String, default: "Optional" -variable :BUFFER_LINKEDIN_ID, :String, default: "Optional" -variable :BUFFER_PROFILE_ID, :Integer, default: 0 -variable :BUFFER_TWITTER_ID, :String, default: "Optional" -variable :BUFFER_LISTINGS_PROFILE, :String, default: "Optional" - -# Cloudinary for image resizing and cache?? -# (https://cloudinary.com/documentation/rails_integration) -variable :CLOUDINARY_API_KEY, :String, default: "Optional" -variable :CLOUDINARY_API_SECRET, :String, default: "Optional" -variable :CLOUDINARY_CLOUD_NAME, :String, default: "Optional" -variable :CLOUDINARY_SECURE, :String, default: "Optional" - -# HTML/CSS to Image for generating social preview images -# (https://docs.htmlcsstoimage.com/example-code/ruby) -variable :HCTI_API_USER_ID, :String, default: "Optional" -variable :HCTI_API_KEY, :String, default: "Optional" - -# Fastly for edge caching -# (https://docs.fastly.com/api/) -variable :FASTLY_API_KEY, :String, default: "" -variable :FASTLY_SERVICE_ID, :String, default: "" - -# Honeycomb for monitoring and observability -# (https://www.honeycomb.io/) -variable :HONEYCOMB_API_KEY, :String, default: "" - -# Google analytic -# (https://developers.google.com/analytics/devguides/reporting/core/v4) -variable :GA_TRACKING_ID, :String, default: "Optional" - -# Mailchimp for mails -# (https://mailchimp.com/developer/) -variable :MAILCHIMP_API_KEY, :String, default: "Optional-valid" - -# Pusher for DEV Connect/notfications -# (https://pusher.com/docs) -variable :PUSHER_APP_ID, :String, default: "Optional" -variable :PUSHER_CLUSTER, :String, default: "Optional" -variable :PUSHER_KEY, :String, default: "Optional" -variable :PUSHER_SECRET, :String, default: "Optional" -variable :PUSHER_BEAMS_ID, :String, default: "Optional" -variable :PUSHER_BEAMS_KEY, :String, default: "Optional" - -# Google recaptcha -# (https://developers.google.com/recaptcha/intro) -variable :RECAPTCHA_SECRET, :String, default: "Optional" -variable :RECAPTCHA_SITE, :String, default: "Optional" - -# Slack for customer alerts -# (https://api.slack.com/) -variable :SLACK_CHANNEL, :String, default: "" -variable :SLACK_DEPLOY_CHANNEL, :String, default: "" -variable :SLACK_WEBHOOK_URL, :String, default: "" - -# Stripe for payment system -# (https://stripe.com/docs/api) -variable :STRIPE_PUBLISHABLE_KEY, :String, default: "Optional" -variable :STRIPE_SECRET_KEY, :String, default: "Optional" - -# For video calling in DEV Connect -# (https://www.twilio.com/docs/video) -variable :TWILIO_ACCOUNT_SID, :String, default: "Optional" -variable :TWILIO_VIDEO_API_KEY, :String, default: "Optional" -variable :TWILIO_VIDEO_API_SECRET, :String, default: "Optional" - -# For Twitch live stream integration -# (https://dev.twitch.tv/docs) -variable :TWITCH_CLIENT_ID, :String, default: "Optional" -variable :TWITCH_CLIENT_SECRET, :String, default: "Optional" -variable :TWITCH_WEBHOOK_SECRET, :String, default: "Optional" - -# For calling the Stack Exchange API -# (https://api.stackexchange.com/docs) -variable :STACK_EXCHANGE_APP_KEY, :String, default: "" - -group :production do - variable :SECRET_KEY_BASE, :String - - # Sendgrid - # (https://sendgrid.com/docs/API_Reference/api_v3.html) - variable :SENDGRID_USERNAME_ACCEL, :String - variable :SENDGRID_PASSWORD_ACCEL, :String - - # Heroku - variable :HEROKU_APP_URL, :String # practicaldev.herokuapp.com -end diff --git a/Gemfile b/Gemfile index 8a821b878..0c98e2a64 100644 --- a/Gemfile +++ b/Gemfile @@ -36,7 +36,6 @@ gem "doorkeeper", "~> 5.4" # Oauth 2 provider gem "elasticsearch", "~> 7.8" # Powers DEVs core search functionality gem "email_validator", "~> 2.0" # Email validator for Rails and ActiveModel gem "emoji_regex", "~> 3.0" # A pair of Ruby regular expressions for matching Unicode Emoji symbols -gem "envied", "~> 0.9" # Ensure presence and type of your app's ENV-variables gem "faraday-http-cache", "~> 2.2" # Middleware to handle HTTP caching gem "fastly", "~> 2.5" # Client library for the Fastly acceleration system gem "feedjira", "~> 3.1" # A feed fetching and parsing library @@ -135,6 +134,7 @@ group :development, :test do gem "amazing_print", "~> 1.2" # Great Ruby debugging companion: pretty print Ruby objects to visualize their structure gem "bullet", "~> 6.1" # help to kill N+1 queries and unused eager loading gem "capybara", "~> 3.33" # Capybara is an integration testing tool for rack based web applications + gem "dotenv-rails", "~> 2.7.6" # For loading ENV variables locally gem "faker", "~> 2.13" # A library for generating fake data such as names, addresses, and phone numbers gem "knapsack_pro", "~> 2.1.1" # Help parallelize Ruby spec builds gem "pry-byebug", "~> 3.8" # Combine 'pry' with 'byebug'. Adds 'step', 'next', 'finish', 'continue' and 'break' commands to control execution diff --git a/Gemfile.lock b/Gemfile.lock index 7b70a90bf..3b2ecb9ec 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -252,6 +252,10 @@ GEM unf (>= 0.0.5, < 1.0.0) doorkeeper (5.4.0) railties (>= 5) + dotenv (2.7.6) + dotenv-rails (2.7.6) + dotenv (= 2.7.6) + railties (>= 3.2) elasticsearch (7.8.1) elasticsearch-api (= 7.8.1) elasticsearch-transport (= 7.8.1) @@ -267,8 +271,6 @@ GEM activemodel emoji_regex (3.0.0) enumerable-statistics (2.0.1) - envied (0.9.3) - thor (~> 0.15) environs (1.1.0) equalizer (0.0.11) erb_lint (0.0.35) @@ -880,10 +882,10 @@ DEPENDENCIES devise_invitable (~> 2.0.0) dogstatsd-ruby (~> 4.8) doorkeeper (~> 5.4) + dotenv-rails (~> 2.7.6) elasticsearch (~> 7.8) email_validator (~> 2.0) emoji_regex (~> 3.0) - envied (~> 0.9) erb_lint (~> 0.0.34) exifr (>= 1.3.6) factory_bot_rails (~> 6.1) diff --git a/app/views/admin/configs/show.html.erb b/app/views/admin/configs/show.html.erb index 967e2fdec..8ccdd51d5 100644 --- a/app/views/admin/configs/show.html.erb +++ b/app/views/admin/configs/show.html.erb @@ -884,13 +884,11 @@ - <% ENVied.config.variables.each do |v| %> - <% if v.group.in?(ENVied.required_groups) %> - - <%= v.name %> - <%= ApplicationConfig[v.name] %> - - <% end %> + <% ENV.sort.each do |key, value| %> + + <%= key %> + <%= value %> + <% end %> diff --git a/bin/setup b/bin/setup index 384ca0121..a4286a6fb 100755 --- a/bin/setup +++ b/bin/setup @@ -25,6 +25,10 @@ chdir APP_ROOT do # Install JavaScript dependencies if using Yarn system("bin/yarn") + # TEMPORARY @sre will remove before Sept 1, 2020 + puts "\n== Create .env File from application.yml ==" + system!("bin/rails create_dot_env_file") + puts "\n== Copying sample files ==" unless File.exist?("config/database.yml") cp "config/database.yml.sample", "config/database.yml" diff --git a/config/.env_sample b/config/.env_sample new file mode 100644 index 000000000..3f100f258 --- /dev/null +++ b/config/.env_sample @@ -0,0 +1,156 @@ +# App domain + Protocol setting for development +export APP_DOMAIN="localhost:3000" +export APP_PROTOCOL="http://" + +# Community Related Variables +export COMMUNITY_COPYRIGHT_START_YEAR="2016" +export COMMUNITY_NAME="DEV(local)" + +# Email related variables +export DEFAULT_EMAIL="yo@dev.to" + +# Service timeout length +export RACK_TIMEOUT_WAIT_TIMEOUT=100_000 +export RACK_TIMEOUT_SERVICE_TIMEOUT=100_000 + +# Heroku release slug used to bust caches on deploys +export HEROKU_SLUG_COMMIT="Optional" + +# Redis caching storage +export REDIS_URL="redis://localhost:6379" + +# Redis sessions storage +export REDIS_SESSIONS_URL="redis://localhost:6379" +export SESSION_KEY="_Dev_Community_Session" +export SESSION_EXPIRY_SECONDS=1209600 # two weeks in seconds + +# Redis Sidekiq storage +export REDIS_SIDEKIQ_URL="redis://localhost:6379" + +# Elasticsearch +export ELASTICSEARCH_URL="http://localhost:9200" + +# SSL config +export FORCE_SSL_IN_RAILS="not applicable in dev" + +################################################ +############## 3rd Party Services ############## +################################################ + +# It's best if you have the following configured +# for more development experience + +# GitHub +# (https://developer.github.com/v3/guides/basics-of-authentication/) +export GITHUB_KEY="" +export GITHUB_SECRET="" + +# Twitter +# (https://developer.twitter.com/en/docs/basics/authentication/oauth-2-0/application-only) +export TWITTER_KEY="" +export TWITTER_SECRET="" + +################################################ +######### Optional 3rd Party Services ########## +################################################ + +# Honeybadger for error tracking +# (https://docs.honeybadger.io/lib/ruby/getting-started/introduction.html) +export HONEYBADGER_API_KEY="Optional" +export HONEYBADGER_JS_API_KEY="Optional" + +# AWS for images storages +export AWS_ID="Optional" +export AWS_SECRET="Optional" +export AWS_BUCKET_NAME="Optional" +export AWS_UPLOAD_REGION="" + +# AWS SDK +# (https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html) +export AWS_DEFAULT_REGION="us-east-1" +export AWS_SDK_KEY="Optional" +export AWS_SDK_SECRET="Optional" + +# AWS for video storage +export AWS_S3_INPUT_BUCKET="Optional" +export AWS_S3_VIDEO_ID="Optional" +export AWS_S3_VIDEO_KEY="Optional" + +# Buffer for sending to buffer +# (https://buffer.com/developers/api) +export BUFFER_ACCESS_TOKEN="Optional" +export BUFFER_FACEBOOK_ID="Optional" +export BUFFER_LINKEDIN_ID="Optional" +export BUFFER_PROFILE_ID=0 +export BUFFER_TWITTER_ID="Optional" +export BUFFER_LISTINGS_PROFILE="Optional" + +# Cloudinary for image resizing and cache?? +# (https://cloudinary.com/documentation/rails_integration) +export CLOUDINARY_API_KEY="Optional" +export CLOUDINARY_API_SECRET="Optional" +export CLOUDINARY_CLOUD_NAME="Optional" +export CLOUDINARY_SECURE="Optional" + +# HTML/CSS to Image for generating social preview images +# (https://docs.htmlcsstoimage.com/example-code/ruby) +export HCTI_API_USER_ID="Optional" +export HCTI_API_KEY="Optional" + +# Fastly for edge caching +# (https://docs.fastly.com/api/) +export FASTLY_API_KEY="" +export FASTLY_SERVICE_ID="" + +# Honeycomb for monitoring and observability +# (https://www.honeycomb.io/) +export HONEYCOMB_API_KEY="" + +# Google analytic +# (https://developers.google.com/analytics/devguides/reporting/core/v4) +export GA_TRACKING_ID="Optional" + +# Mailchimp for mails +# (https://mailchimp.com/developer/) +export MAILCHIMP_API_KEY="Optional-valid" + +# Pusher for DEV Connect/notfications +# (https://pusher.com/docs) +export PUSHER_APP_ID= +export PUSHER_CLUSTER= +export PUSHER_KEY= +export PUSHER_SECRET= +export PUSHER_BEAMS_ID= +export PUSHER_BEAMS_KEY= + +# Google recaptcha +# (https://developers.google.com/recaptcha/intro) +export RECAPTCHA_SECRET="Optional" +export RECAPTCHA_SITE="Optional" + +# Slack for customer alerts +# (https://api.slack.com/) +export SLACK_CHANNEL="" +export SLACK_DEPLOY_CHANNEL="" +export SLACK_WEBHOOK_URL="" + +# Stripe for payment system +# (https://stripe.com/docs/api) +export STRIPE_PUBLISHABLE_KEY="Optional" +export STRIPE_SECRET_KEY="Optional" + +# For video calling in DEV Connect +# (https://www.twilio.com/docs/video) +export TWILIO_ACCOUNT_SID="Optional" +export TWILIO_VIDEO_API_KEY="Optional" +export TWILIO_VIDEO_API_SECRET="Optional" + +# For Twitch live stream integration +# (https://dev.twitch.tv/docs) +export TWITCH_CLIENT_ID="Optional" +export TWITCH_CLIENT_SECRET="Optional" +export TWITCH_WEBHOOK_SECRET="Optional" + +# For calling the Stack Exchange API +# (https://api.stackexchange.com/docs) +export STACK_EXCHANGE_APP_KEY="" diff --git a/config/application.rb b/config/application.rb index 6cfa51c4f..19bfdd8ae 100644 --- a/config/application.rb +++ b/config/application.rb @@ -17,6 +17,8 @@ require "sprockets/railtie" # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) +Dotenv::Railtie.load if Rails.env.test? || Rails.env.development? + module PracticalDeveloper class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. diff --git a/config/environments/production.rb b/config/environments/production.rb index 828346228..dacaf4b37 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -110,11 +110,12 @@ Rails.application.configure do # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false - config.app_domain = ENV["APP_DOMAIN"] + config.app_domain = ENV["APP_DOMAIN"] || "localhost:3000" + protocol = ENV["APP_PROTOCOL"] || "http://" config.action_mailer.delivery_method = :smtp config.action_mailer.perform_deliveries = true - config.action_mailer.default_url_options = { host: ENV["APP_PROTOCOL"] + ENV["APP_DOMAIN"] } + config.action_mailer.default_url_options = { host: protocol + config.app_domain } ActionMailer::Base.smtp_settings = { address: "smtp.sendgrid.net", port: "587", @@ -125,14 +126,14 @@ Rails.application.configure do enable_starttls_auto: true } - if ENV["HEROKU_APP_URL"] != ENV["APP_DOMAIN"] + if ENV["HEROKU_APP_URL"] != config.app_domain config.middleware.use Rack::HostRedirect, - ENV["HEROKU_APP_URL"] => ENV["APP_DOMAIN"] + ENV["HEROKU_APP_URL"] => config.app_domain end end # rubocop:enable Metrics/BlockLength Rails.application.routes.default_url_options = { host: Rails.application.config.app_domain, - protocol: ENV["APP_PROTOCOL"].delete_suffix("://") + protocol: (ENV["APP_PROTOCOL"] || "http://").delete_suffix("://") } diff --git a/config/initializers/0_application_config.rb b/config/initializers/0_application_config.rb index 154af296f..d7e5d698e 100644 --- a/config/initializers/0_application_config.rb +++ b/config/initializers/0_application_config.rb @@ -1,15 +1,19 @@ -ENVied.require(*ENV["ENVIED_GROUPS"] || Rails.groups) - class ApplicationConfig URI_REGEXP = %r{(?https?://)?(?.+?)(?:\d+)?$}.freeze def self.[](key) - ENVied.public_send(key) + if ENV.key?(key) + ENV[key] + else + Rails.logger.warn("Unset ENV variable: #{key}.") + nil + end end def self.app_domain_no_port app_domain = self["APP_DOMAIN"] - match = app_domain.match(URI_REGEXP) - match[:host] + return unless app_domain + + app_domain.match(URI_REGEXP)[:host] end end diff --git a/config/initializers/aws_sdk.rb b/config/initializers/aws_sdk.rb index 4460eb360..05218d72f 100644 --- a/config/initializers/aws_sdk.rb +++ b/config/initializers/aws_sdk.rb @@ -1,5 +1,5 @@ Rails.application.reloader.to_prepare do - AWS_LAMBDA = if Rails.env.production? + AWS_LAMBDA = if Rails.env.production? && ApplicationConfig["AWS_SDK_KEY"].present? Aws::Lambda::Client.new( region: ApplicationConfig["AWS_DEFAULT_REGION"], access_key_id: ApplicationConfig["AWS_SDK_KEY"], diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb index ec320e910..132f71daa 100644 --- a/config/initializers/carrierwave.rb +++ b/config/initializers/carrierwave.rb @@ -27,7 +27,7 @@ CarrierWave.configure do |config| } config.asset_host = "https://#{ApplicationConfig['APP_DOMAIN']}/images" config.fog_public = false - else + elsif ApplicationConfig["AWS_ID"].present? region = ApplicationConfig["AWS_UPLOAD_REGION"].presence || ApplicationConfig["AWS_DEFAULT_REGION"] config.fog_credentials = { provider: "AWS", @@ -35,6 +35,10 @@ CarrierWave.configure do |config| aws_secret_access_key: ApplicationConfig["AWS_SECRET"], region: region } + else + # Fallback on file storage if AWS creds are not present + config.asset_host = "https://#{ApplicationConfig['APP_DOMAIN']}/images" + config.storage = :file end config.fog_directory = ApplicationConfig["AWS_BUCKET_NAME"] config.storage = :fog diff --git a/config/initializers/pusher.rb b/config/initializers/pusher.rb index 47a2e7858..7b0dfbf9a 100644 --- a/config/initializers/pusher.rb +++ b/config/initializers/pusher.rb @@ -1,13 +1,15 @@ require "pusher/push_notifications" -Pusher.app_id = ApplicationConfig["PUSHER_APP_ID"] -Pusher.key = ApplicationConfig["PUSHER_KEY"] -Pusher.secret = ApplicationConfig["PUSHER_SECRET"] -Pusher.cluster = ApplicationConfig["PUSHER_CLUSTER"] -Pusher.logger = Rails.logger -Pusher.encrypted = true +if ApplicationConfig["PUSHER_APP_ID"].present? + Pusher.app_id = ApplicationConfig["PUSHER_APP_ID"] + Pusher.key = ApplicationConfig["PUSHER_KEY"] + Pusher.secret = ApplicationConfig["PUSHER_SECRET"] + Pusher.cluster = ApplicationConfig["PUSHER_CLUSTER"] + Pusher.logger = Rails.logger + Pusher.encrypted = true -Pusher::PushNotifications.configure do |config| - config.instance_id = ApplicationConfig["PUSHER_BEAMS_ID"] - config.secret_key = ApplicationConfig["PUSHER_BEAMS_KEY"] + Pusher::PushNotifications.configure do |config| + config.instance_id = ApplicationConfig["PUSHER_BEAMS_ID"] + config.secret_key = ApplicationConfig["PUSHER_BEAMS_KEY"] + end end diff --git a/config/initializers/slack_notifier.rb b/config/initializers/slack_notifier.rb index 7bda893d0..be6a289f8 100644 --- a/config/initializers/slack_notifier.rb +++ b/config/initializers/slack_notifier.rb @@ -4,14 +4,6 @@ class NoOpHTTPClient end end -def create_normal_notifier - Slack::Notifier.new( - ApplicationConfig["SLACK_WEBHOOK_URL"], - channel: ApplicationConfig["SLACK_CHANNEL"], - username: "activity_bot", - ) -end - def create_test_channel_notifier return create_stubbed_notifier if ApplicationConfig["SLACK_WEBHOOK_URL"].blank? @@ -41,7 +33,7 @@ def init_slack_client } end - webhook_url = ApplicationConfig["SLACK_WEBHOOK_URL"] + webhook_url = ApplicationConfig["SLACK_WEBHOOK_URL"] || "" use_no_op_client = Rails.env.test? || webhook_url.blank? Slack::Notifier.new(webhook_url) do diff --git a/config/sample_application.yml b/config/sample_application.yml index 32b00715e..c94c2f6ce 100644 --- a/config/sample_application.yml +++ b/config/sample_application.yml @@ -1,14 +1,156 @@ -GITHUB_KEY: -GITHUB_SECRET: +# App domain + Protocol setting for development +APP_DOMAIN: "localhost:3000" +APP_PROTOCOL: "http://" -TWITTER_KEY: -TWITTER_SECRET: +# Community Related Variables +COMMUNITY_COPYRIGHT_START_YEAR: "2016" +COMMUNITY_NAME: "DEV(local)" -PUSHER_APP_ID: -PUSHER_KEY: -PUSHER_SECRET: -PUSHER_CLUSTER: +# Email related variables +DEFAULT_EMAIL: "yo@dev.to" -TWITCH_CLIENT_ID: -TWITCH_CLIENT_SECRET: -TWITCH_WEBHOOK_SECRET: +# Service timeout length +RACK_TIMEOUT_WAIT_TIMEOUT: 100_000 +RACK_TIMEOUT_SERVICE_TIMEOUT: 100_000 + +# Heroku release slug used to bust caches on deploys +HEROKU_SLUG_COMMIT: "Optional" + +# Redis caching storage +REDIS_URL: "redis://localhost:6379" + +# Redis sessions storage +REDIS_SESSIONS_URL: "redis://localhost:6379" +SESSION_KEY: "_Dev_Community_Session" +SESSION_EXPIRY_SECONDS: 1209600 # two weeks in seconds + +# Redis Sidekiq storage +REDIS_SIDEKIQ_URL: "redis://localhost:6379" + +# Elasticsearch +ELASTICSEARCH_URL: "http://localhost:9200" + +# SSL config +FORCE_SSL_IN_RAILS: "not applicable in dev" + +################################################ +############## 3rd Party Services ############## +################################################ + +# It's best if you have the following configured +# for more development experience + +# GitHub +# (https://developer.github.com/v3/guides/basics-of-authentication/) +GITHUB_KEY: "" +GITHUB_SECRET: "" + +# Twitter +# (https://developer.twitter.com/en/docs/basics/authentication/oauth-2-0/application-only) +TWITTER_KEY: "" +TWITTER_SECRET: "" + +################################################ +######### Optional 3rd Party Services ########## +################################################ + +# Honeybadger for error tracking +# (https://docs.honeybadger.io/lib/ruby/getting-started/introduction.html) +HONEYBADGER_API_KEY: "Optional" +HONEYBADGER_JS_API_KEY: "Optional" + +# AWS for images storages +AWS_ID: "Optional" +AWS_SECRET: "Optional" +AWS_BUCKET_NAME: "Optional" +AWS_UPLOAD_REGION: "" + +# AWS SDK +# (https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html) +AWS_DEFAULT_REGION: "us-east-1" +AWS_SDK_KEY: "Optional" +AWS_SDK_SECRET: "Optional" + +# AWS for video storage +AWS_S3_INPUT_BUCKET: "Optional" +AWS_S3_VIDEO_ID: "Optional" +AWS_S3_VIDEO_KEY: "Optional" + +# Buffer for sending to buffer +# (https://buffer.com/developers/api) +BUFFER_ACCESS_TOKEN: "Optional" +BUFFER_FACEBOOK_ID: "Optional" +BUFFER_LINKEDIN_ID: "Optional" +BUFFER_PROFILE_ID: 0 +BUFFER_TWITTER_ID: "Optional" +BUFFER_LISTINGS_PROFILE: "Optional" + +# Cloudinary for image resizing and cache?? +# (https://cloudinary.com/documentation/rails_integration) +CLOUDINARY_API_KEY: "Optional" +CLOUDINARY_API_SECRET: "Optional" +CLOUDINARY_CLOUD_NAME: "Optional" +CLOUDINARY_SECURE: "Optional" + +# HTML/CSS to Image for generating social preview images +# (https://docs.htmlcsstoimage.com/example-code/ruby) +HCTI_API_USER_ID: "Optional" +HCTI_API_KEY: "Optional" + +# Fastly for edge caching +# (https://docs.fastly.com/api/) +FASTLY_API_KEY: "" +FASTLY_SERVICE_ID: "" + +# Honeycomb for monitoring and observability +# (https://www.honeycomb.io/) +HONEYCOMB_API_KEY: "" + +# Google analytic +# (https://developers.google.com/analytics/devguides/reporting/core/v4) +GA_TRACKING_ID: "Optional" + +# Mailchimp for mails +# (https://mailchimp.com/developer/) +MAILCHIMP_API_KEY: "Optional-valid" + +# Pusher for DEV Connect/notfications +# (https://pusher.com/docs) +PUSHER_APP_ID: "Optional" +PUSHER_CLUSTER: "Optional" +PUSHER_KEY: "Optional" +PUSHER_SECRET: "Optional" +PUSHER_BEAMS_ID: "Optional" +PUSHER_BEAMS_KEY: "Optional" + +# Google recaptcha +# (https://developers.google.com/recaptcha/intro) +RECAPTCHA_SECRET: "Optional" +RECAPTCHA_SITE: "Optional" + +# Slack for customer alerts +# (https://api.slack.com/) +SLACK_CHANNEL: "" +SLACK_DEPLOY_CHANNEL: "" +SLACK_WEBHOOK_URL: "" + +# Stripe for payment system +# (https://stripe.com/docs/api) +STRIPE_PUBLISHABLE_KEY: "Optional" +STRIPE_SECRET_KEY: "Optional" + +# For video calling in DEV Connect +# (https://www.twilio.com/docs/video) +TWILIO_ACCOUNT_SID: "Optional" +TWILIO_VIDEO_API_KEY: "Optional" +TWILIO_VIDEO_API_SECRET: "Optional" + +# For Twitch live stream integration +# (https://dev.twitch.tv/docs) +TWITCH_CLIENT_ID: "Optional" +TWITCH_CLIENT_SECRET: "Optional" +TWITCH_WEBHOOK_SECRET: "Optional" + +# For calling the Stack Exchange API +# (https://api.stackexchange.com/docs) +STACK_EXCHANGE_APP_KEY: "" diff --git a/docs/backend/configuration.md b/docs/backend/configuration.md index 4d15fcc42..5f25d802b 100644 --- a/docs/backend/configuration.md +++ b/docs/backend/configuration.md @@ -6,21 +6,20 @@ title: Configuration We currently use the following gems for configuring the application: -- [ENVied](https://github.com/eval/envied) +- [dotenv](https://github.com/bkeepers/dotenv) - [rails-settings-cached](https://github.com/huacnlee/rails-settings-cached) - [vault](https://github.com/hashicorp/vault-ruby) -## ENVied +## dotenv -This gem is primarily used for configuring environment variables related to -credentials and third party services. Examples: +This gem is used for configuring environment variables for test and development +environments. Examples: - `REDIS_URL` - `FASTLY_API_KEY` - `STRIPE_SECRET_KEY` -Settings managed via ENVied can be found in -[`Envfile`](https://github.com/forem/forem/blob/master/Envfile) (see +Settings managed via your ENV can be found in [Configuring Environment Variables](../getting-started/config-env.md)) and viewed at `/admin/config` (see [the Admin guide](../admin/readme.md)): diff --git a/docs/faqs.md b/docs/faqs.md index bb19ce99f..f65c2e099 100644 --- a/docs/faqs.md +++ b/docs/faqs.md @@ -20,8 +20,8 @@ By default Rails logs to `log.development.log`. If, instead, you wish to log to `STDOUT` you can add the variable: -```yaml -RAILS_LOG_TO_STDOUT: true +```shell +export RAILS_LOG_TO_STDOUT=true ``` -to your own `config/application.yml` file. +to your own `.env` file. diff --git a/docs/getting-started/config-env.md b/docs/getting-started/config-env.md index 2a2027e14..d7e34ec6b 100644 --- a/docs/getting-started/config-env.md +++ b/docs/getting-started/config-env.md @@ -10,27 +10,27 @@ provides a fake default for any missing keys. The [backend guide][backend_guide] will show you how to get free API keys for additional services that may be required to run certain parts of the app. -To set up keys for your local instance of Forem, you'll need to create an -`application.yml` file. You can do this by copying the file called -`sample_application.yml` in the `config` directory: +To set up keys for your local instance of Forem, you'll need to create an `.env` +file. You can do this by copying the file called `.env_sample` in the `config` +directory: ```shell -cp config/sample_application.yml config/application.yml +cp config/.env_sample .env ``` -Then, add each key you need to the `application.yml` file. For example, if -you're setting up GitHub authentication: +Then, add each key you need to the `.env` file. For example, if you're setting +up GitHub authentication: ```shell -GITHUB_KEY: "SOME_REAL_SECURE_KEY_HERE" -GITHUB_SECRET: "ANOTHER_REAL_SECURE_KEY_HERE" +export GITHUB_KEY="SOME_REAL_SECURE_KEY_HERE" +export GITHUB_SECRET="ANOTHER_REAL_SECURE_KEY_HERE" ``` -(Don't worry, your `application.yml` file is ignored by git) +(Don't worry, your `.env` file is ignored by git) -If you are missing `ENV` variables on bootup, the [envied][envied] gem will -alert you with messages similar to -`'error_on_missing_variables!': The following environment variables should be set: A_MISSING_KEY.`. +If you are missing `ENV` variables when you boot your application you will see a +warning message in your logs when you try to access that variable +`Unset ENV variable: xyz`. Only certain features require "real" keys, so you may be able to add them as you work on different areas of the application. diff --git a/docs/installation/containers.md b/docs/installation/containers.md index 36f8defd4..5ca6283ff 100644 --- a/docs/installation/containers.md +++ b/docs/installation/containers.md @@ -67,16 +67,15 @@ with Podman. You can install it by following these additional services that may be required to run certain parts of the app. - For any key that you wish to enter/replace: - 1. Create `config/application.yml` by copying from the provided template - (i.e. with bash: - `cp config/sample_application.yml config/application.yml`). This is a - personal file that is ignored in git. + 1. Create `.env` by copying from the provided template (i.e. with bash: + `cp config/.env_sample .env`). This is a personal file that is ignored + in git. 1. Obtain the development variable and apply the key you wish to enter/replace. i.e.: ```shell - GITHUB_KEY: "SOME_REAL_SECURE_KEY_HERE" - GITHUB_SECRET: "ANOTHER_REAL_SECURE_KEY_HERE" + export GITHUB_KEY="SOME_REAL_SECURE_KEY_HERE" + export GITHUB_SECRET="ANOTHER_REAL_SECURE_KEY_HERE" ``` - You do not need "real" keys for basic development. Some features require diff --git a/docs/installation/linux.md b/docs/installation/linux.md index 357f0539e..171e37466 100644 --- a/docs/installation/linux.md +++ b/docs/installation/linux.md @@ -97,16 +97,15 @@ NOTE: Make sure to download **the OSS version**, `elasticsearch-oss`. additional services that may be required to run certain parts of the app. - For any key that you wish to enter/replace: - 1. Create `config/application.yml` by copying from the provided template - (i.e. with bash: - `cp config/sample_application.yml config/application.yml`). This is a - personal file that is ignored in git. + 1. Create `.env` by copying from the provided template (i.e. with bash: + `cp config/.env_sample .env`). This is a personal file that is ignored + in git. 1. Obtain the development variable and apply the key you wish to enter/replace. i.e.: ```shell - GITHUB_KEY: "SOME_REAL_SECURE_KEY_HERE" - GITHUB_SECRET: "ANOTHER_REAL_SECURE_KEY_HERE" + export GITHUB_KEY="SOME_REAL_SECURE_KEY_HERE" + export GITHUB_SECRET="ANOTHER_REAL_SECURE_KEY_HERE" ``` - If you are missing `ENV` variables on bootup, the diff --git a/docs/installation/mac.md b/docs/installation/mac.md index 9d1abce07..b3442abeb 100644 --- a/docs/installation/mac.md +++ b/docs/installation/mac.md @@ -217,16 +217,15 @@ your local Elasticsearch installation, for example: additional services that may be required to run certain parts of the app. - For any key that you wish to enter/replace, follow the steps below. - 1. Create `config/application.yml` by copying from the provided template - (i.e. with bash: - `cp config/sample_application.yml config/application.yml`). This is a - personal file that is ignored in git. + 1. Create `.env` by copying from the provided template (i.e. with bash: + `cp config/.env_sample .env`). This is a personal file that is ignored + in git. 2. Obtain the development variable and apply the key you wish to enter/replace. i.e.: ```shell - GITHUB_KEY: "SOME_REAL_SECURE_KEY_HERE" - GITHUB_SECRET: "ANOTHER_REAL_SECURE_KEY_HERE" + export GITHUB_KEY="SOME_REAL_SECURE_KEY_HERE" + export GITHUB_SECRET="ANOTHER_REAL_SECURE_KEY_HERE" ``` - If you are missing `ENV` variables on bootup, the diff --git a/docs/installation/postgresql.md b/docs/installation/postgresql.md index 8a475107b..2bd9ccce2 100644 --- a/docs/installation/postgresql.md +++ b/docs/installation/postgresql.md @@ -32,17 +32,17 @@ The go into depth on how Rails merges the existing `database.yml` with the connection string. -### Setup `DATABASE_URL` in application.yml +### Setup `DATABASE_URL` in .env -1. Open your `config/application.yml` +1. Open your `.env` 1. Add the following: -```yml -DATABASE_URL: postgresql://USERNAME:PASSWORD@localhost +```shell +export DATABASE_URL=postgresql://USERNAME:PASSWORD@localhost # Optional: If your test database is in a different url, be sure to set this. -DATABASE_URL_TEST: postgresql://USERNAME:PASSWORD@localhost +export DATABASE_URL_TEST=postgresql://USERNAME:PASSWORD@localhost ``` 1. Replace `USERNAME` with your database username, `PASSWORD` with your database diff --git a/docs/installation/vault.md b/docs/installation/vault.md index 6e0f34e30..7a565af3b 100644 --- a/docs/installation/vault.md +++ b/docs/installation/vault.md @@ -99,11 +99,11 @@ The final command will give you the following output: The `token` output from above is what you will use in your application to access Vault. All that is left to do is set the appropriate ENV variables in your -`application.yml` file. +`.env` file. -```yml -VAULT_TOKEN: important-policy-token -VAULT_SECRET_NAMESPACE: +```shell +export VAULT_TOKEN=important-policy-token export VAULT_SECRET_NAMESPACE= ``` Restart your application to start using Vault. One easy way to see it in action diff --git a/docs/installation/windows.md b/docs/installation/windows.md index 5e6bb4749..d6c0b7b2f 100644 --- a/docs/installation/windows.md +++ b/docs/installation/windows.md @@ -254,16 +254,15 @@ To install Elasticsearch perform the following steps: additional services that may be required to run certain parts of the app. - For any key that you wish to enter/replace: - 1. Create `config/application.yml` by copying from the provided template - (ie. with bash: - `cp config/sample_application.yml config/application.yml`). This is a - personal file that is ignored in git. + 1. Create `.env` by copying from the provided template (ie. with bash: + `cp config/.env_sample .env`). This is a personal file that is ignored + in git. 1. Obtain the development variable and apply the key you wish to enter/replace. ie: ```shell - GITHUB_KEY: "SOME_REAL_SECURE_KEY_HERE" - GITHUB_SECRET: "ANOTHER_REAL_SECURE_KEY_HERE" + export GITHUB_KEY="SOME_REAL_SECURE_KEY_HERE" + export GITHUB_SECRET="ANOTHER_REAL_SECURE_KEY_HERE" ``` - If you are missing `ENV` variables on bootup, the @@ -329,8 +328,7 @@ There are currently two work-arounds. 1. `docker run -d --name selenium-hub -p 4444:4444 selenium/hub:3.141.59-20200409` 2. `CH=$(docker run --rm --name=ch --link selenium-hub:hub -v /dev/shm:/dev/shm selenium/node-chrome:3.141.59-20200409)` - 3. Add `SELENIUM_URL: "http://localhost:4444/wd/hub"` to your - `application.yml` + 3. Add `export SELENIUM_URL="http://localhost:4444/wd/hub"` to your `.env` 4. Run your System test! 2. Port forward with `socats` (more info needed). diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 49c4829c3..60c1700cb 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -96,8 +96,8 @@ refer to ## CORS If you are experiencing CORS issues locally or need to display more information -about the CORS headers, add the following variable to your `application.yml`: +about the CORS headers, add the following variable to your `.env`: -```yml -DEBUG_CORS: true +```shell +export DEBUG_CORS=true ``` diff --git a/lib/tasks/temporary/env_variables.rake b/lib/tasks/temporary/env_variables.rake new file mode 100644 index 000000000..003622788 --- /dev/null +++ b/lib/tasks/temporary/env_variables.rake @@ -0,0 +1,29 @@ +# Create a .env file from your application.yml file +task create_dot_env_file: :environment do + exit if File.file?(".env") + + # Used to set all defaults we have if not set already in application.yml + sample_app_yml = YAML.load_file("config/sample_application.yml") + + # read existing lines + application_yml = File.open("config/application.yml", 'r').read + + + File.open(".env", "w") do |env_file| + # add default values only if they are not already present in the + # application.yml file which would result in a duplicate key + sample_app_yml.each_pair do |variable, value| + env_file.write("export #{variable}=#{value}\n") unless application_yml.include?(variable.to_s) + end + + File.open("config/application.yml", 'r') do |file| + file.each_line do |line| + new_line = line.gsub(": ", "=") + unless new_line.blank? || new_line.starts_with?("#") + new_line.prepend("export ") + end + env_file.write(new_line) + end + end + end +end diff --git a/spec/initializers/application_config_spec.rb b/spec/initializers/application_config_spec.rb index 6a7829d5f..3c216b61c 100644 --- a/spec/initializers/application_config_spec.rb +++ b/spec/initializers/application_config_spec.rb @@ -1,6 +1,12 @@ require "rails_helper" describe ApplicationConfig do + it "logs warning if key is not found" do + allow(Rails.logger).to receive(:warn) + described_class["missing"] + expect(Rails.logger).to have_received(:warn).with("Unset ENV variable: missing.") + end + describe ".app_domain_no_port" do it "handles plain subdomain only name" do setup_app_domain("renner") diff --git a/vendor/cache/dotenv-2.7.6.gem b/vendor/cache/dotenv-2.7.6.gem new file mode 100644 index 0000000000000000000000000000000000000000..c3a4147271be05b6d7a1bee4eebbf69e61e5c191 GIT binary patch literal 12800 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 t<c>OOcR9r_(-!$&X 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 literal 0 HcmV?d00001 diff --git a/vendor/cache/envied-0.9.3.gem b/vendor/cache/envied-0.9.3.gem deleted file mode 100644 index 6e33088aefe8a67b45ec59a158a30f0dfad84797..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23552 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�eoI6rl9Ze&FLB;w+mjw7 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%0DW0MjL3#q?G9Do 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 z