Merge conflicts

pull/1009/head
Rick Carlino 2018-10-14 17:22:45 -05:00
commit 1e04d54992
26 changed files with 140 additions and 85 deletions

View File

@ -0,0 +1,13 @@
DOCKER_COMPOSE_VERSION=1.22.0
ADMIN_PASSWORD=this_is_a_fake_admin_pass
POSTGRES_PASSWORD=fake_db_password_also
API_HOST=10.1.10.219
MQTT_HOST=10.1.10.219
API_PORT=3000
# These are fake, also, don't worry.
DEVISE_SECRET=9542adda400ea2d10644d09006b098ce3d245d72740c442f6c37116a3cea2c2e8cbde7a7e451e9c4d3b1db826f4a4991bfd8ba50a8b313704bca89289615cd3f
SECRET_KEY_BASE=9390020153a289e9b7349b77864dd765d6e88a8fdd23727085ea82609992133152f2cb437db2abae15a61af79087bd37e3fb3a4470107effde0791a0acec27cc
CI=true
CODECLIMATE_REPO_TOKEN=2216bf71b977a85ed8da497942c34a503dbedd77da1b6b97c33551ba02ea02f8
COVERALLS_REPO_TOKEN=lEX6nkql7y2YFCcIXVq5ORvdvMtYzfZdG
CODECOV_TOKEN=c0fe1e65-d284-4d58-a742-4088e88be35d

View File

@ -0,0 +1,27 @@
version: 2
# executor: "machine"
jobs:
build:
executor: "machine"
steps:
- checkout
- run:
name: Setup the database and (fake) secrets
command: |
curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-`uname -s`-`uname -m` > docker-compose
chmod +x docker-compose
sudo mv docker-compose /usr/local/bin
mv .circleci/circle_envs .env
sudo docker-compose run web bundle install
sudo docker-compose run web npm install
sudo docker-compose run web bundle exec rails db:setup
sudo docker-compose run web rake keys:generate
- run:
name: Run Rails and JS tests
command: |
sudo docker-compose run web rspec spec
sudo docker-compose run webpack npm run tslint
sudo docker-compose run webpack npm run sass-lint
sudo docker-compose run webpack npm run typecheck
sudo docker-compose run webpack npm run test-slow
sudo docker-compose run webpack npm run coverage

View File

@ -1,39 +0,0 @@
language: node_js
node_js:
- 8.11.3
cache:
# yarn: true
directories:
- /home/travis/.rvm/
- /home/travis/bundle
env:
global:
- ADMIN_PASSWORD=not_a_real_password
- SECRET_TOKEN=e815982094c62436066bafc9151f2d33c4a351a776654cb7487476de260a4592
- OS_UPDATE_SERVER=http://example.com
- FW_UPDATE_SERVER=http://example.com
- DB=postgresql
- COVERALLS_REPO_TOKEN=lEX6nkql7y2YFCcIXVq5ORvdvMtYzfZdG
matrix:
# Production like setup
- MQTT_HOST=example.com
# Self hosted like setup
- MQTT_HOST=127.0.0.1
API_HOST=127.0.0.1
NO_EMAILS=TRUE
before_install:
- rvm install 2.5.1
- rvm use 2.5.1
install:
- npm install
- bundle install --jobs=3 --retry=3 --path=/home/travis/bundle
before_script:
- cp config/database.travis.yml config/database.yml
- bundle exec rake db:create db:migrate
script:
- bundle exec rspec --fail-fast=3
- npm run tslint
- npm run sass-lint
- npm run typecheck
- npm run test-slow
- npm run coverage

View File

@ -3,7 +3,7 @@ ruby "2.5.1"
gem "active_model_serializers"
gem "bunny"
gem "delayed_job_active_record"
gem "delayed_job_active_record" # TODO: Get off of SQL backed jobs. Use Redis
gem "delayed_job"
gem "devise"
gem "discard"
@ -31,7 +31,7 @@ gem "webpack-rails"
# Still working out the bugs. - RC 5 Jul 18
gem "rabbitmq_http_api_client"
gem "zero_downtime_migrations"
# gem "digest-murmurhash"
gem "redis", "~> 4.0"
group :development, :test do
gem "thin"

View File

@ -262,6 +262,7 @@ GEM
rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0)
rake (12.3.1)
redis (4.0.2)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
@ -389,6 +390,7 @@ DEPENDENCIES
rails
rails-erd
rails_12factor
redis (~> 4.0)
request_store
rollbar
rspec

View File

@ -25,6 +25,8 @@ class RmqConfigWriter
"using 3rd party MQTT hosting, please set this value and "\
"re-build the server image."
TEMPLATE = <<~END
# THIS FILE WAS GENERATED BY THE `RmqConfigWriter` CLASS IN THE RAILS APP
# == DO NOT ATTEMPT TO MODIFY MANUALLY - IT WILL BE OVERWRITTEN ===
auth_backends.1 = cache
auth_cache.cache_ttl = 600000
auth_cache.cached_backend = http
@ -41,7 +43,6 @@ class RmqConfigWriter
END
def self.do_render
puts "Writing RMQ Config ================================================="
raise BAD_PASSWORD if ADMIN_PASSWORD.length < 5
FileUtils.mkdir_p CONFIG_PATH
File.open(CONFIG_OUTPUT, "w+") { |f| f.write(TEMPLATE % CFG_DATA) }

View File

@ -9,15 +9,15 @@ Bundler.require(:default, Rails.env)
module FarmBot
class Application < Rails::Application
Delayed::Worker.max_attempts = 4
# config.after_initialize do
# Bullet.enable = true
# Bullet.console = true
# end
REDIS_ENV_KEY = ENV.fetch("WHERE_IS_REDIS_URL", "REDIS_URL")
REDIS_URL = ENV.fetch(REDIS_ENV_KEY, "redis://redis:6379/0")
config.cache_store = :redis_cache_store, { url: REDIS_URL }
config.middleware.use Rack::Attack
config.active_record.schema_format = :sql
config.active_job.queue_adapter = :delayed_job
config.active_job.queue_adapter = :delayed_job
config.action_dispatch.perform_deep_munge = false
I18n.enforce_available_locales = false
LOCAL_API_HOST = ENV["API_HOST"] || "localhost"
LOCAL_API_HOST = ENV.fetch("API_HOST", "webpack")
WEBPACK_URL = "http://#{LOCAL_API_HOST}:3808"
config.webpack.dev_server.host = proc { request.host }
# PROBLEM: Containers run in docker. Default dev_server.manifest_host is
@ -26,7 +26,7 @@ module FarmBot
# SOLUTION: Explicitly set the hostname of the container where Webpack runs.
# In our case, that's `webpack`. See docker-compose.yml for all
# hostnames. -RC 1 OCT 18
config.webpack.dev_server.manifest_host = "webpack"
config.webpack.dev_server.manifest_host = LOCAL_API_HOST
config.webpack.dev_server.manifest_port = 3808
config.generators do |g|
g.template_engine :erb

View File

@ -1,10 +1,10 @@
FarmBot::Application.configure do
config
.action_mailer
.default_url_options = { host: ENV.fetch("API_HOST", "my.farmbot.io") }
config.active_support.deprecation = :notify
config.cache_classes = true
config.cache_store = :null_store
config.consider_all_requests_local = false
config.eager_load = true
config.force_ssl = true if ENV["FORCE_SSL"]

View File

@ -0,0 +1,4 @@
# Some services do not use ENV vars as the configuration mechanism (I wish they
# would). To simplify setup for self-hosters, we perform magic in the background
# that converts ENV vars into config files. - RC
RmqConfigWriter.render

View File

@ -1,16 +1,8 @@
class Rack::Attack
### Throttle Spammy Clients ###
throttle('req/ip', limit: 100, period: 1.minutes) do |req|
throttle('req/ip', limit: 1000, period: 1.minutes) do |req|
req.ip
end
### Prevent Brute-Force Login Attacks ###
# Throttle requests to /sign_in by IP address
throttle('logins/ip', limit: 5, period: 20.seconds) do |req|
if req.path.include?('/sign_in') && req.post?
req.ip
end
end
end
# Always allow requests from localhost

View File

@ -1,2 +0,0 @@
RmqConfigWriter.render

View File

@ -1,26 +1,40 @@
version: '3.4'
x-db_user: &db_user
depends_on: ["db"]
depends_on: ["db", "redis"]
x-rails: &rails
image: farmbot_web
env_file: .env
volumes:
- .:/farmbot
- ./docker_volumes/bundle_cache:/bundle
x-base_config: &base_config
env_file: .env
# restart: unless-stopped
services:
redis:
<<: *base_config
image: redis
volumes:
- ./docker_volumes/redis/data:/data
- ./docker_volumes/redis/conf:/usr/local/etc/redis
ports: ["6379:6379"]
db: # ====================
<<: *base_config
image: postgres
volumes: ["./docker_volumes/db:/var/lib/postgresql/data"]
env_file: .env
web: # ====================
<<: *base_config
<<: *db_user
<<: *rails
build:
context: .
dockerfile: docker_configs/api.Dockerfile
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -e development -p ${API_PORT:-3000} -b 0.0.0.0"
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -e development -p 3000 -b 0.0.0.0"
ports: ["3000:3000"] # Web / API
mqtt: # ====================
<<: *base_config
build:
context: ./docker_configs
dockerfile: rabbitmq.Dockerfile
@ -31,24 +45,30 @@ services:
- "3002:15675" # MQTT over WebSockets
- "15672:15672" # Management API
depends_on: ["web"]
env_file: .env
environment:
- RABBITMQ_CONFIG_FILE=/farmbot/farmbot_rmq_config
volumes:
- ./docker_volumes/rabbit:/farmbot
webpack: # ====================
<<: *rails
<<: *base_config
image: node:8.12.0
working_dir: /farmbot
command: ./node_modules/.bin/webpack-dev-server --config config/webpack.config.js
volumes:
- .:/farmbot
ports: ["3808:3808"] # Webpack Dev Server
delayed_job: # ====================
<<: *base_config
<<: *rails
depends_on: ["db","mqtt"]
command: bundle exec rake jobs:work
log_digests: # ====================
<<: *base_config
<<: *rails
<<: *db_user
command: bundle exec rake api:log_digest
rabbit_jobs: # ====================
<<: *base_config
<<: *rails
depends_on: ["db","mqtt"]
command: bundle exec rails r lib/rabbit_workers.rb

View File

@ -63,7 +63,14 @@ FORCE_SSL=Remove this if not using HTTPS://
# If running a FarmBot setup for personal use or none of the above apply, you
# can safely delete the rest of this file.
# Only relevant if you use Heroku or pay a 3rd party vendor for Redis hosting.
# Most users can delete this.
# If your Heroku Redis vendor uses a custom `REDIS_URL` ENV var such as
# `REDISTOGO_URL`, set the value here. If you delete this line, the app will
# default to `REDIS_URL`.
WHERE_IS_REDIS_URL=REDISTOGO_URL # Just an example. Change or delete.
# Delete this if you don't use 3rd party Redis hosting. See WHERE_IS_REDIS_URL
REDIS_URL=redis://redis:6379/0
# For email delivery. Who is your email host?
SMTP_HOST=smtp.sendgrid.net

View File

@ -111,11 +111,6 @@ namespace :api do
sh "sudo docker-compose up --scale webpack=0"
end
desc "Run Webpack _ONLY_. No other services"
task webpack: :environment do
sh "sudo docker-compose run webpack npm run webpack"
end
desc "Pull the latest Farmbot API version"
task(update: :environment) { same_thing }

View File

@ -14,7 +14,7 @@
"start": "echo 'use `sudo docker-compose up` instead.'",
"heroku-postbuild": "webpack --config=./config/webpack.prod.js",
"webpack": "./node_modules/.bin/webpack-dev-server --config config/webpack.dev.js",
"test-slow": "jest --coverage --no-cache -w 4",
"test-slow": "jest --coverage --ci --maxWorkers=6",
"test": "jest --no-coverage --cache -w 5",
"typecheck": "./node_modules/.bin/tsc --noEmit --jsx preserve",
"tslint": "./node_modules/tslint/bin/tslint --project .",
@ -29,8 +29,8 @@
"webpack-dev-server": "3.1.9"
},
"dependencies": {
"@blueprintjs/core": "^3.6.1",
"@blueprintjs/datetime": "^3.2.0",
"@blueprintjs/core": "^3.7.0",
"@blueprintjs/datetime": "^3.3.0",
"@blueprintjs/select": "^3.2.0",
"@types/enzyme": "3.1.14",
"@types/fastclick": "^1.0.28",
@ -51,7 +51,7 @@
"css-loader": "1.0.0",
"enzyme": "^3.7.0",
"enzyme-adapter-react-16": "^1.6.0",
"farmbot": "^6.5.2",
"farmbot": "6.5.2",
"farmbot-toastr": "^1.0.3",
"fastclick": "^1.0.6",
"file-loader": "2.0.0",
@ -94,7 +94,7 @@
"which": "1.3.1"
},
"devDependencies": {
"webpack-cli": "3.1.1"
"webpack-cli": "^3.1.2"
},
"jest": {
"clearMocks": true,

View File

@ -0,0 +1,21 @@
#!/bin/sh
sudo docker-compose run web npm run tslint &
P1=$!
sudo docker-compose run web npm run sass-lint &
P2=$!
sudo docker-compose run web npm run typecheck &
P3=$!
sudo docker-compose run web rspec spec &
P4=$!
sudo docker-compose run web npm run test-slow &
P5=$!
sudo docker-compose run web npm run coverage &
P6=$!
wait $P1 $P2 $P3 $P4 $P5 $P6

View File

@ -7,6 +7,7 @@ describe Devices::Dump do
NOPE = "Expected value[%s] to equal %s. Got %s instead"
it "serializes _all_ the data", :slow do
Rails.cache.clear
device = FactoryBot.create(:device)
resources = MODEL_NAMES
.map { |x| x.to_s.singularize.to_sym }
@ -47,7 +48,7 @@ describe Devices::Dump do
expect(results[:export_created_at]).to be
export_time = Time.parse(results[:export_created_at])
today = Time.now
expect(today - export_time).to be < 1
expect(today - export_time.round).to be < 1
expect(results[:database_schema])
.to eq(ActiveRecord::Migrator.current_version)
expect(results[:server_url]).to eq($API_URL)

View File

@ -27,10 +27,10 @@ cd Farmbot-Web-App
# == Nothing will work if you skip this step!!! ==
snap install micro --classic # Don't like `micro`? vim, nano, etc are fine, too.
# READ NOTE ABOVE. Very important!
cp example.env .env # ⚠ SKIP THIS STEP IF UPGRADING!
micro .env # ⚠ SKIP THIS STEP IF UPGRADING!
# ^ This is the most important step
# READ NOTE ABOVE. Very important!
# Install application specific Ruby dependencies
sudo docker-compose run web bundle install

View File

@ -1,3 +1,4 @@
jest.resetAllMocks();
jest.mock("farmbot-toastr", () => ({
fun: jest.fn(),
init: jest.fn(),

View File

@ -3,11 +3,9 @@ import { Dictionary } from "farmbot";
templateSettings.interpolate = /{{([\s\S]+?)}}/g;
const mockTemplate = template;
jest.mock("i18next", () => ({
t: (i: string, translation: Dictionary<string> = {}): string => {
const precompiledTemplate = mockTemplate(i);
const precompiledTemplate = template(i);
return precompiledTemplate(translation);
},
init: jest.fn()

View File

@ -64,6 +64,7 @@ describe("<App />: Loading", () => {
it("MUST_LOADs not loaded", () => {
const wrapper = mount(<App {...fakeProps()} />);
expect(wrapper.text()).toContain("Loading...");
wrapper.unmount();
});
it("MUST_LOADs partially loaded", () => {
@ -71,6 +72,7 @@ describe("<App />: Loading", () => {
p.loaded = ["Sequence"];
const wrapper = mount(<App {...p} />);
expect(wrapper.text()).toContain("Loading...");
wrapper.unmount();
});
it("MUST_LOADs loaded", () => {
@ -78,6 +80,7 @@ describe("<App />: Loading", () => {
p.loaded = ["Sequence", "Regimen", "FarmEvent", "Point"];
const wrapper = mount(<App {...p} />);
expect(wrapper.text()).not.toContain("Loading...");
wrapper.unmount();
});
});
@ -86,10 +89,12 @@ describe("<App />: NavBar", () => {
const wrapper = mount(<App {...fakeProps()} />);
expect(wrapper.text())
.toContain("Farm DesignerControlsDeviceSequencesRegimensToolsFarmware");
wrapper.unmount();
});
it("displays ticker", () => {
const wrapper = mount(<App {...fakeProps()} />);
expect(wrapper.text()).toContain("No logs yet.");
wrapper.unmount();
});
});

View File

@ -29,6 +29,8 @@ describe("<ControlsPopup />", () => {
p.axisInversion.x = true;
const wrapper = mount(<ControlsPopup {...p} />);
afterAll(wrapper.unmount);
it("Has a false initial state", () => {
expect(wrapper.state("isOpen")).toBeFalsy();
});

View File

@ -31,5 +31,7 @@ describe("<CrashPage/>", () => {
jest.resetAllMocks();
el.find("a").first().simulate("click");
expect(Session.clear).toHaveBeenCalled();
el.unmount();
});
});

View File

@ -38,5 +38,6 @@ describe("<Link/>", () => {
function Child(_: unknown) { return <p>Hey!</p>; }
const el = shallow(<Link to="/wherever"><Child /></Link>);
expect(el.html()).toContain("Hey!");
el.unmount();
});
});

View File

@ -9,6 +9,7 @@ describe("<LoadingPlant/>", () => {
expect(wrapper.find(".loading-plant-text").props().y).toEqual(150);
expect(wrapper.text()).toContain("Loading");
expect(wrapper.find(".animate").length).toEqual(0);
wrapper.unmount();
});
it("renders loading animation", () => {
@ -21,5 +22,6 @@ describe("<LoadingPlant/>", () => {
expect(wrapper.find(".loading-plant-text").props().y).toEqual(435);
expect(wrapper.text()).toContain("Loading");
expect(wrapper.find(".animate").length).toEqual(1);
wrapper.unmount();
});
});

View File

@ -30,14 +30,16 @@ describe("<RootComponent />", () => {
it("clears session when not authorized", () => {
mockAuth = undefined;
mockPathname = "/app/account";
shallow(<RootComponent store={store} />);
const wrapper = shallow(<RootComponent store={store} />);
expect(Session.clear).toHaveBeenCalled();
wrapper.unmount();
});
it("authorized", () => {
mockAuth = auth;
mockPathname = "/app/account";
shallow(<RootComponent store={store} />);
const wrapper = shallow(<RootComponent store={store} />);
expect(Session.clear).not.toHaveBeenCalled();
wrapper.unmount();
});
});