Skip to content

Commit

Permalink
Generators (#110)
Browse files Browse the repository at this point in the history
feat: add a setup generator
  • Loading branch information
bibendi authored and palkan committed Jan 24, 2020
1 parent b934944 commit 42e611d
Show file tree
Hide file tree
Showing 16 changed files with 439 additions and 8 deletions.
8 changes: 3 additions & 5 deletions .dockerdev/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
version: '3.4'
version: '2.4'

services:
app:
image: ruby:2.6
environment:
- GEM_HOME=/bundle
- BUNDLE_PATH=/bundle
- HISTFILE=/app/tmp/.bash_history
working_dir: /app
volumes:
- ..:/app
- bundle:/bundle
- gems:/usr/local/bundle

volumes:
bundle:
gems:
10 changes: 10 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,39 @@ Style/BlockDelimiters:
Metrics/MethodLength:
Exclude:
- 'spec/**/*.rb'
- 'lib/rails/generators/**/*.rb'

Metrics/LineLength:
Max: 100
Exclude:
- 'spec/**/*.rb'
- 'lib/rails/generators/**/*.rb'

Metrics/AbcSize:
Exclude:
- 'spec/**/*.rb'
- 'lib/rails/generators/**/*.rb'

Metrics/BlockLength:
Exclude:
- '*.gemspec'
- 'spec/**/*.rb'
- 'anycable-rails.gemspec'
- 'lib/rails/generators/**/*.rb'

Metrics/ClassLength:
Exclude:
- 'lib/rails/generators/**/*.rb'

Metrics/CyclomaticComplexity:
Exclude:
- 'spec/**/*.rb'
- 'lib/rails/generators/**/*.rb'

Metrics/PerceivedComplexity:
Exclude:
- 'spec/**/*.rb'
- 'lib/rails/generators/**/*.rb'

Lint/HandleExceptions:
Enabled: false
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## master

- Add set up generator to configure a Rails application by running `bin/rails g anycable:setup`. ([@bibendi][])

- Require a minimum version of Ruby when installing the gem. ([@bibendi][])

- Add ability to develop the gem with Docker. ([@bibendi][])
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,25 @@ gem "redis", ">= 4.0"

(and don't forget to run `bundle install`).

Next, specify AnyCable subscription adapter for Action Cable:
### Interactive set up

After the gem was installed, you can run an interactive wizard to configure your Rails application for using with AnyCable by running a generator:

```sh
bin/rails g anycable:setup
```

### Manual set up

Specify AnyCable subscription adapter for Action Cable:

```yml
# config/cable.yml
production:
development:
adapter: any_cable # or anycable

production:
adapter: any_cable
```
and specify AnyCable WebSocket server URL:
Expand Down
3 changes: 2 additions & 1 deletion anycable-rails.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ Gem::Specification.new do |spec|
"source_code_uri" => "http://github.com/anycable/anycable-rails"
}

spec.files = `git ls-files README.md MIT-LICENSE CHANGELOG.md lib`.split
spec.files = Dir.glob("lib/**/*") + %w[README.md MIT-LICENSE CHANGELOG.md]
spec.require_paths = ["lib"]

spec.required_ruby_version = ">= 2.4"

spec.add_dependency "anycable", "~> 0.6.0"
spec.add_dependency "rails", ">= 5"

spec.add_development_dependency "ammeter", "~> 1.1"
spec.add_development_dependency "bundler", ">= 1.10"
spec.add_development_dependency "pry-byebug"
spec.add_development_dependency "rake", "~> 10.0"
Expand Down
235 changes: 235 additions & 0 deletions lib/rails/generators/anycable/setup/setup_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
# frozen_string_literal: true

module AnyCableRailsGenerators
# Entry point for interactive installation
class SetupGenerator < ::Rails::Generators::Base
namespace "anycable:setup"
source_root File.expand_path("templates", __dir__)

METHODS = %w[skip local docker].freeze
SERVER_VERSION = "v0.6.4"
OS_NAMES = %w[linux darwin freebsd win].freeze
CPU_NAMES = %w[amd64 arm64 386 arm].freeze
SERVER_SOURCES = %w[skip brew binary].freeze
DEFAULT_BIN_PATH = "/usr/local/bin"

class_option :method,
type: :string,
desc: "Select your development environment (options: #{METHODS.join(', ')})"
class_option :source,
type: :string,
desc: "Choose a way of installing AnyCable-Go server (options: #{SERVER_SOURCES.join(', ')})"
class_option :bin_path,
type: :string,
desc: "Where to download AnyCable-Go server binary (default: #{DEFAULT_BIN_PATH})"
class_option :os,
type: :string,
desc: "Specify the OS for AnyCable-Go server binary (options: #{OS_NAMES.join(', ')})"
class_option :cpu,
type: :string,
desc: "Specify the CPU architecturefor AnyCable-Go server binary (options: #{CPU_NAMES.join(', ')})"
class_option :skip_heroku,
type: :boolean,
desc: "Do not copy Heroku configs"
class_option :skip_procfile_dev,
type: :boolean,
desc: "Do not create Procfile.dev"

def welcome
say "👋 Welcome to AnyCable interactive installer."
end

def configs
inside("config") do
template "cable.yml"
template "anycable.yml"
end
end

def cable_url
environment(nil, env: :development) do
<<~SNIPPET
# Specify AnyCable WebSocket server URL to use by JS client
config.action_cable.url = ENV.fetch("CABLE_URL", "ws://localhost:3334/cable").presence
SNIPPET
end

environment(nil, env: :production) do
<<~SNIPPET
# Specify AnyCable WebSocket server URL to use by JS client
config.action_cable.url = ENV["CABLE_URL"].presence
SNIPPET
end

say_status :info, "✅ 'config.action_cable.url' has been configured"
say_status :help, "⚠️ Make sure you have `action_cable_meta_tag` in your application.html if you're using JS client"
end

def development_method
answer = METHODS.index(options[:method]) || 99

until METHODS[answer.to_i]
answer = ask "Which environment do you use for development? (1) Local, (2) Docker, (0) Skip"
end

case env = METHODS[answer.to_i]
when "skip"
say_status :help, "⚠️ Please, read this guide on how to install AnyCable-Go server 👉 https://docs.anycable.io/#/anycable-go/getting_started", :yellow
else
send "install_for_#{env}"
end
end

def heroku
if options[:skip_heroku].nil?
return unless yes? "Do you use Heroku for deployment?"
elsif options[:skip_heroku]
return
end

template "Procfile"
inside("bin") { template "heroku-web" }

say_status :help, "️️⚠️ Please, read the required steps to configure Heroku applications 👉 https://docs.anycable.io/#/deployment/heroku", :yellow
end

def finish
say_status :info, "✅ AnyCable has been configured successfully!"
end

private

def install_for_docker
say_status :help, "️️⚠️ Docker development configuration could vary", :yellow

say "Here is an example snippet for docker-compose.yml:"
say <<~YML
─────────────────────────────────────────
anycable-ws:
image: anycable/anycable-go:v0.6.4
ports:
- '3334:3334'
environment:
PORT: 3334
REDIS_URL: redis://redis:6379/0
ANYCABLE_RPC_HOST: anycable-rpc:50051
depends_on:
- anycable-rpc
- redis
anycable-rpc:
<<: *backend
command: bundle exec anycable
ports:
- '50051'
─────────────────────────────────────────
YML
end

def install_for_local
install_server
template_proc_files
end

def install_server
answer = SERVER_SOURCES.index(options[:source]) || 99

until SERVER_SOURCES[answer.to_i]
answer = ask "How do you want to install AnyCable-Go WebSocket server? (1) Homebrew, (2) Download binary, (0) Skip"
end

case answer.to_i
when 0
say_status :help, "⚠️ Please, read this guide on how to install AnyCable-Go server 👉 https://docs.anycable.io/#/anycable-go/getting_started", :yellow
return
else
return unless send("install_from_#{SERVER_SOURCES[answer.to_i]}")
end

say_status :info, "✅ AnyCable-Go WebSocket server has been successfully installed"
end

def template_proc_files
file_name = "Procfile.dev"

if File.exist?(file_name)
append_file file_name, 'anycable: bundle exec anycable --server-command "anycable-go --port 3334"'
else
say_status :help, "💡 We recommend using Hivemind to manage multiple processes in development 👉 https://github.com/DarthSim/hivemind", :yellow

if options[:skip_procfile_dev].nil?
return unless yes? "Do you want to create a '#{file_name}' file?"
elsif options[:skip_procfile_dev]
return
end

template file_name
end
end

def install_from_brew
run "brew install anycable-go", abort_on_failure: true
run "anycable-go -v", abort_on_failure: true
end

def install_from_binary
out = options[:bin_path] if options[:bin_path]
out ||= "/usr/local/bin" if options[:method] # User don't want interactive mode
out ||= ask "Please, enter the path to download the AnyCable-Go binary to", default: DEFAULT_BIN_PATH, path: true

os_name = OS_NAMES.find(&Gem::Platform.local.os.method(:==)) ||
options[:os] ||
ask("What is your OS name?", limited_to: OS_NAMES)

cpu_name = CPU_NAMES.find(&current_cpu.method(:==)) ||
options[:cpu] ||
ask("What is your CPU architecture?", limited_to: CPU_NAMES)

download_exe(
"https://github.com/anycable/anycable-go/releases/download/#{SERVER_VERSION}/" \
"anycable-go-#{SERVER_VERSION}-#{os_name}-#{cpu_name}",
to: out,
file_name: "anycable-go"
)

true
end

def download_exe(url, to:, file_name:)
file_path = File.join(to, file_name)

run "#{sudo(to)}curl -L #{url} -o #{file_path}", abort_on_failure: true
run "#{sudo(to)}chmod +x #{file_path}", abort_on_failure: true
run "#{file_path} -v", abort_on_failure: true
end

def sudo!(path)
sudo = ""
unless File.writable?(path)
if yes? "Path is not writable 😕. Do you have sudo privileges?"
sudo = "sudo "
else
say_status :error, "❌ Failed to install AnyCable-Go WebSocket server", :red
raise StandardError, "Path #{path} is not writable!"
end
end

sudo
end

def current_cpu
case Gem::Platform.local.cpu
when "x86_64", "x64"
"amd64"
when "x86_32", "x86", "i386", "i486", "i686"
"i386"
when "aarch64", "aarch64_be", /armv8/
"arm64"
when "arm", /armv7/, /armv6/
"arm"
else
"unknown"
end
end
end
end
2 changes: 2 additions & 0 deletions lib/rails/generators/anycable/setup/templates/Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
web: bin/heroku-web
release: bundle exec rails db:migrate
3 changes: 3 additions & 0 deletions lib/rails/generators/anycable/setup/templates/Procfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
server: bin/rails server
assets: bin/webpack-dev-server
anycable: bundle exec anycable --server-command "anycable-go --port 3334"
7 changes: 7 additions & 0 deletions lib/rails/generators/anycable/setup/templates/bin/heroku-web
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

if [ "$ANYCABLE_DEPLOYMENT" == "true" ]; then
bundle exec anycable --server-command="anycable-go"
else
bundle exec rails server -p $PORT -b 0.0.0.0
fi
30 changes: 30 additions & 0 deletions lib/rails/generators/anycable/setup/templates/config/anycable.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This file contains per-environment settings for AnyCable.
#
# Since AnyCable config is based on anyway_config (https://github.com/palkan/anyway_config), all AnyCable settings
# can be set or overridden through the corresponding environment variables.
# E.g., `rpc_host` is overridden by ANYCABLE_RPC_HOST, `debug` by ANYCABLE_DEBUG etc.
#
# Note that AnyCable recognizes REDIS_URL env variable for Redis pub/sub adapter. If you want to
# use another Redis instance for AnyCable, provide ANYCABLE_REDIS_URL variable.
#
# Read more about AnyCable configuration here: https://docs.anycable.io/#/ruby/configuration
#
default: &default
# Turn on/off access logs ("Started..." and "Finished...")
access_logs_disabled: false
# This is the host and the port to run AnyCable RPC server on.
# You must configure your WebSocket server to connect to it, e.g.:
# $ anycable-go --rpc-host="<rpc hostname>:50051"
rpc_host: "127.0.0.1:50051"
# Whether to enable gRPC level logging or not
log_grpc: false
# Use the same channel name for WebSocket server, e.g.:
# $ anycable-go --redis-channel="__anycable__"
redis_channel: "__anycable__"

development:
<<: *default
redis_url: "redis://localhost:6379/1"

production:
<<: *default
Loading

0 comments on commit 42e611d

Please sign in to comment.