Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deploy notifications #606

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ spec/dummy/public/uploads/*

/vendor
docker-compose.override.yml
.env
.env
mysql
node_modules
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ PATH
jquery-ui-rails (~> 6.0.1)
judge (~> 3.1.0)
judge-simple_form (~> 1.1.0)
jwt (~> 2.8.0)
kaminari
mini_magick
rails (>= 5.0)
Expand Down Expand Up @@ -115,6 +116,7 @@ GEM
bundler
rake
thor (>= 0.14.0)
base64 (0.2.0)
bcrypt (3.1.16)
better_errors (2.9.1)
coderay (>= 1.0.0)
Expand Down Expand Up @@ -240,6 +242,8 @@ GEM
judge-simple_form (1.1.0)
judge (>= 2.0)
simple_form (>= 3.0)
jwt (2.8.0)
base64
kaminari (1.2.2)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2)
Expand Down
4 changes: 4 additions & 0 deletions app/assets/javascripts/fae/form/_validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,10 @@ Fae.form.validator = {
);
_this.addCustomValidation();
}
// See https://github.com/wearefine/fae/issues/409
// This was preventing the functionality of a checkbox field
// on the user form.
_this.$password_field.data('validate', '');
},

/**
Expand Down
1 change: 0 additions & 1 deletion app/controllers/fae/deploy_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,5 @@ def deploy_site
end
render json: {success: false}
end

end
end
28 changes: 28 additions & 0 deletions app/controllers/fae/netlify_hooks_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Fae
class NetlifyHooksController < ActionController::Base

def netlify_hook
unless Rails.env.test?
signature = request.headers["X-Webhook-Signature"]
if signature.blank?
Rails.logger.info 'request.headers["X-Webhook-Signature"] header is missing'
return head :forbidden
end
if Fae.netlify[:notification_hook_signature].blank?
Rails.logger.info "Fae.netlify[:notification_hook_signature] is not set"
return head :forbidden
end

options = {iss: "netlify", verify_iss: true, algorithm: "HS256"}
decoded = JWT.decode(signature, Fae.netlify[:notification_hook_signature], true, options)
unless decoded.first['sha256'] == Digest::SHA256.hexdigest(request.body.read)
Rails.logger.info "Netlify hook signature mismatch, check the value of Fae.netlify[:notification_hook_signature] against the value of the JWS secret token in the Netlify webhook settings."
return head :forbidden
end
end

DeployNotifications.notify_admins(request).deliver_now
return head :ok
end
end
end
31 changes: 31 additions & 0 deletions app/mailers/fae/deploy_notifications.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module Fae
class DeployNotifications < ActionMailer::Base
default from: Fae.devise_mailer_sender
layout 'layouts/fae/mailer'

def notify_admins(request_obj, additional_emails = [])
body = request_obj.body.read
if body.blank?
Rails.logger.info "DeployNotifications.notify_admins called without a body"
return
end
body = JSON.parse(body)

# Don't notify if the deploy is a code push
if body['commit_ref'].present?
Rails.logger.info "#{body['context']} - #{body['id']} is a code push, skipping notification"
Rails.logger.info "#{body['branch']} #{body['commit_ref']}: #{body['commit_message']}"
return
end

@deploy = body
@base_url = request_obj.base_url
recipients = Fae::User.where(receive_deploy_notifications: true).pluck(:email)
recipients += additional_emails
@fae_options = Fae::Option.instance
current_time_in_zone = Time.now.in_time_zone(@fae_options.time_zone).strftime('%Y-%m-%d %l:%M %p')
subject = "#{@fae_options.title} Deploy Notification #{current_time_in_zone}"
mail(to: recipients, subject: subject)
end
end
end
5 changes: 5 additions & 0 deletions app/models/fae/deploy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Fae
class Deploy < ApplicationRecord
belongs_to :user
end
end
2 changes: 1 addition & 1 deletion app/services/fae/netlify_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def get_deploys
def run_deploy(deploy_hook_type, current_user)
hook = Fae::DeployHook.find_by_environment(deploy_hook_type)
if hook.present?
post("#{hook.url}?trigger_title=#{current_user.full_name.gsub(' ', '+')}+triggered+a+#{deploy_hook_type.titleize}+deploy")
post("#{hook.url}?trigger_title=#{current_user.full_name.gsub(' ', '+')}+triggered+a+#{deploy_hook_type.titleize}+deploy.")
return true
end
false
Expand Down
9 changes: 9 additions & 0 deletions app/views/fae/deploy_notifications/notify_admins.text.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<% if @deploy['title'].present? %>
<%= @deploy['title'].gsub('+', ' ') %>
<% end %>

<% if @deploy['state'] == 'ready' %>
The deploy was successful.
<% else %>
There was an issue with your deployment. Attempt to redeploy and if the issue persists contact your CMS administrator or see <%= @base_url %>/admin/help.
<% end %>
1 change: 1 addition & 0 deletions app/views/fae/users/_form.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
= fae_input f, :first_name
= fae_input f, :last_name
= fae_input f, :email
= fae_input f, :receive_deploy_notifications, helper_text: t('fae.user.receive_deploy_notifications_hint')
= fae_input f, :password, helper_text: t('fae.user.password_hint')
= fae_input f, :password_confirmation
- if current_user.admin? || current_user.super_admin?
Expand Down
1 change: 1 addition & 0 deletions app/views/layouts/fae/mailer.text.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
= yield
1 change: 1 addition & 0 deletions config/locales/fae.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ en:
edit: 'Edit'
user:
password_hint: 'To update your password, fill out the fields below. Otherwise leave blank. Passwords must contain at least 8 characters.'
receive_deploy_notifications_hint: 'Check to receive deploy notifications via email.'
header: 'Your Settings'
last_login: 'Last Logged In'
active: 'Active'
Expand Down
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@

get 'deploy/deploys_list' => 'deploy#deploys_list', as: 'deploy_deploys_list'
post 'deploy/deploy_site' => 'deploy#deploy_site', as: 'deploy_deploy_site'

post 'netlify_hooks/netlify_hook' => 'netlify_hooks#netlify_hook', as: 'netlify_hook'

## catch all 404
match "*path" => 'pages#error404', via: [:get, :post]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AddReceiveDeployNotificationsToFaeUsers < ActiveRecord::Migration[7.0]
def change
add_column :fae_users, :receive_deploy_notifications, :boolean, default: false
add_index :fae_users, :receive_deploy_notifications
end
end
13 changes: 13 additions & 0 deletions db/migrate/20240222165620_create_fae_deploys.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class CreateFaeDeploys < ActiveRecord::Migration[7.0]
def change
create_table :fae_deploys do |t|
t.integer :user_id, index: true
t.string :environment
t.string :deploy_id, index: true
t.string :deploy_status
t.boolean :notified

t.timestamps
end
end
end
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
environment:
- RAILS_ENV=development
- DEV_MYSQL_HOST=db
- FINE_NETLIFY_API_TOKEN=test
- FINE_NETLIFY_API_USER=test
- FINE_NETLIFY_API_TOKEN=abc123
- FINE_NETLIFY_API_USER=[email protected]
db:
# image: postgres
# volumes:
Expand All @@ -37,4 +37,4 @@
ports:
- '3307:3306'
volumes:
- /private/app/dbs:/var/lib/mysql
- ./mysql:/var/lib/mysql
23 changes: 20 additions & 3 deletions docs/features/netlify.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Netlify Deploy Monitor

* [Enabling Deploys](#enabling-deploys)
* [Upgrading](#upgrading)
* [Email Notifications](#email-notifications)

Fae provides and easy integration for statically generated frontends hosted on Netlify. If Fae is delivering content to a statically generated frontend via JSON or GraphQL APIs, updating content in the admin won't trigger a build of the frontend with the new content.
Fae provides an easy integration for statically generated frontends hosted on Netlify. If Fae is delivering content to a statically generated frontend via JSON or GraphQL APIs, updating content in the admin won't trigger a build of the frontend with the new content.

This feature allows a super admin to define multiple deploy environments connected to Netlify deploy hooks. It also establishes a global deploy page so admin and super admin users can trigger builds of any defined Netlify environments.

Expand All @@ -13,8 +13,12 @@ The deploy page will also display the status of the current deploy, along with a

## Enabling Deploys

In your app, run `rake fae:install:migrations` and then migrate the database.

To enable this feature, make sure `config.netlify` is defined in your Fae initializer with all options set correctly.

`notification_hook_signature` is only required if you will use incoming Netlify deploy notification webhooks.

`config/initializers/fae.rb`
```ruby
Fae.setup do |config|
Expand All @@ -26,7 +30,8 @@ Fae.setup do |config|
api_token: 'netlify-api-token',
site: 'site-name-in-netlify',
site_id: 'site-id-in-netlify',
api_base: 'https://api.netlify.com/api/v1/'
api_base: 'https://api.netlify.com/api/v1/',
notification_hook_signature: 'netlify-notification-hook-signature'
}

end
Expand All @@ -35,3 +40,15 @@ end
Then go to the root settings at `/admin/root` while logged in as a super admin and add deploy hooks for each environment you wish to enable in the CMS.

You will see a new nav item labeled "Deploy" that links to `/admin/deploy`. Here you'll be able to trigger and view past deploys.

---

## Email Notifications

In your app, run `rake fae:install:migrations` and then migrate the database.

You can opt-in to email-based notifications for when Netlify deploys complete successfully or fail.

The migration adds a new field to the `Fae::User` model and accompanying checkbox in the user form - `receive_deploy_notifications`, pretty self-explanatory. Any users opted in to this setting will receive the email notifications.

In the https://app.netlify.com dashboard for your project, you'll need to add two new webhook notifications - one for `Deploy succeeded` and `Deploy failed`. Both of these webhooks will point to the same URL: https://your-fae-domain.com/admin/netlify_hooks/netlify_hook (replace your-fae-domain with a real value). Your `JWS secret token` can be any string, but it needs to also be set to, and match what you set it to in the `fae.rb` file's `config.netlify['notification_hook_signature']`.
2 changes: 1 addition & 1 deletion docs/topics/initializer.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Fae's default config can be overwritten in a `config/initializers/fae.rb` file.
| key | type | default | description |
| --- | ---- | ------- | ----------- |
| devise_secret_key | string | | unique Devise hash, generated on `rails g fae:install` |
| devise_mailer_sender | string | "[email protected]" | This email address will get passed to Devise and used as the from address in the password reset emails. |
| devise_mailer_sender | string | "[email protected]" | This email address will get passed to Devise and used as the from address in the password reset emails.
| dashboard_exclusions | array | [] | The dashboard will show all objects with recent activity. |
| max_image_upload_size | integer | 2 | This will set a file size limit on image uploads in MBs. |
| max_file_upload_size | integer | 5 | This will set a file size limit on file uploads in MBs. |
Expand Down
1 change: 1 addition & 0 deletions fae.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Gem::Specification.new do |s|
s.add_dependency 'remotipart', '~> 1.4.0'
s.add_dependency 'simple_form', '<= 5.1'
s.add_dependency 'slim'
s.add_dependency 'jwt', '~> 2.8.0'

s.add_development_dependency 'appraisal'
s.add_development_dependency 'better_errors'
Expand Down
3 changes: 2 additions & 1 deletion lib/fae/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ class Engine < ::Rails::Engine
require 'slim'
require 'kaminari'
require 'fae/version'
require "sprockets/railtie"
require 'sprockets/railtie'
require 'jwt'

config.eager_load_paths += %W(#{config.root}/app)

Expand Down
32 changes: 16 additions & 16 deletions lib/fae/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@ module Fae
# configurable defaults
mattr_accessor :devise_secret_key, :devise_mailer_sender, :dashboard_exclusions, :max_image_upload_size, :max_file_upload_size, :languages, :recreate_versions, :validation_helpers, :track_changes, :tracker_history_length, :slug_separator, :disabled_environments, :per_page, :use_cache, :use_form_manager, :netlify

self.devise_secret_key = ''
self.devise_mailer_sender = '[email protected]'
self.dashboard_exclusions = []
self.max_image_upload_size = 2
self.max_file_upload_size = 5
self.languages = {}
self.recreate_versions = false
self.validation_helpers = ValidationHelperCollection.new
self.track_changes = true
self.tracker_history_length = 15
self.slug_separator = '-'
self.disabled_environments = []
self.per_page = 25
self.use_cache = false
self.use_form_manager = false
self.netlify = {}
self.devise_secret_key = ''
self.devise_mailer_sender = '[email protected]'
self.dashboard_exclusions = []
self.max_image_upload_size = 2
self.max_file_upload_size = 5
self.languages = {}
self.recreate_versions = false
self.validation_helpers = ValidationHelperCollection.new
self.track_changes = true
self.tracker_history_length = 15
self.slug_separator = '-'
self.disabled_environments = []
self.per_page = 25
self.use_cache = false
self.use_form_manager = false
self.netlify = {}

# this function maps the vars from your app into your engine
def self.setup(&block)
Expand Down
3 changes: 2 additions & 1 deletion lib/generators/fae/templates/initializers/fae.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
# api_token: 'netlify-api-token',
# site: 'site-name-in-netlify',
# site_id: 'site-id-in-netlify',
# api_base: 'https://api.netlify.com/api/v1/'
# api_base: 'https://api.netlify.com/api/v1/',
# notification_hook_signature: 'netlify-notification-hook-signature'
# }
end
1 change: 1 addition & 0 deletions spec/dummy/app/assets/javascripts/fae.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//= require fae/vendor/trumbowyg

$(document).ready(function(){

$('.login-body').addClass('test-class');

$("body").on("modal:show", function (e) {
Expand Down
Empty file removed spec/dummy/app/mailers/.keep
Empty file.
2 changes: 2 additions & 0 deletions spec/dummy/config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@
# config.action_view.raise_on_missing_translations = true

config.action_mailer.default_url_options = { :host => 'localhost' }

config.hosts << "21b5-47-26-150-197.ngrok-free.app"
end
9 changes: 5 additions & 4 deletions spec/dummy/config/initializers/fae.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@
config.use_form_manager = true

# Removed for now to simplify render.com deploy
if Rails.env.test?
if Rails.env.test? || Rails.env.development?
config.netlify = {
api_user: ENV['FINE_NETLIFY_API_USER'],
api_token: ENV['FINE_NETLIFY_API_TOKEN'],
site: 'fine-pss',
site_id: 'bb32173b-9ff2-4d9d-860a-2683ae4e1e2b',
api_base: 'https://api.netlify.com/api/v1/'
site: 'nuxt3-pss-deployment-refactor',
site_id: '6b73c3a4-dd29-4acf-b65a-c9745b0f08eb',
api_base: 'https://api.netlify.com/api/v1/',
notification_hook_signature: '123test'
}
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This migration comes from fae (originally 20240220144206)
class AddReceiveDeployNotificationsToFaeUsers < ActiveRecord::Migration[7.0]
def change
add_column :fae_users, :receive_deploy_notifications, :boolean, default: false
add_index :fae_users, :receive_deploy_notifications
end
end
14 changes: 14 additions & 0 deletions spec/dummy/db/migrate/20240222170611_create_fae_deploys.fae.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# This migration comes from fae (originally 20240222165620)
class CreateFaeDeploys < ActiveRecord::Migration[7.0]
def change
create_table :fae_deploys do |t|
t.integer :user_id, index: true
t.string :environment
t.string :deploy_id, index: true
t.string :deploy_status
t.boolean :notified

t.timestamps
end
end
end
Loading