app/controllers/apps_controller.rb

Summary

Maintainability
B
5 hrs
Test Coverage
require 'shellwords'

class AppsController < ApiController
  actions = [:index, :create]
  before_action :find_app, except: actions
  before_action :verify_app_owner, except: actions

  def index
    @apps = current_user.apps
    render status: 200
  end

  def create
    appname = app_params[:name]
    if App.where(name: appname).first
      response = { id: "app.exists", message: "App #{appname} already exists" }
      render json: response, status: 409
    else
      @app = App.new(name: appname, user: current_user)
      if @app.save
        render 'app', status: 200
      else
        response = { id: "app.save.fail",
                     message: "App (name: #{@app.name}) saving has failed" }
        response[:errors] = @app.errors.to_h

        render json: response, status: 422
      end
    end
  end

  def show
    render 'app', status: 200
  end

  def update
    if @app.update(app_params.permit(:name))
      render 'app', status: 200
    else
      head 422
    end
  end

  def get_env
    render 'env', status: 200
  end

  def update_env
    @app.release!(app_params.require(:env))
    render 'env', status: 200
  end

  def destroy
    if @app.destroy
      response = { message: "app has been destroyed" }
      render json: response, status: 200
    else
      head 500
    end
  end

  def formation
    render status: 200
  end

  def scale
    @app.scale(app_params.require(:formation))
    render 'formation', status: 200
  end

  def logs
    opts = { }
    opts[:tail] = true if params[:tail]
    opts[:num]  = params[:num] if params.key?(:num)
    @logs = @app.logs(opts)
    render status: 200
  end

  # starts a one-off container session
  def run
    unless params[:command].present?
      response = { id: "run.command.invalid",
                   message: "invalid command given (#{params[:command]})" }
      render json: response, status: 400
    end

    if env['rack.hijack']
      env['rack.hijack'].call

      socket = env['rack.hijack_io']
      begin
        socket.flush
        socket.sync = true

        container = Docker::Container.create(
          'Image'     => @app.releases.last.image,
          'Cmd'       => Shellwords.split(params[:command]),
          'Tty'       => true,
          'OpenStdin' => true,
          'StdinOnce' => false
        )

        socket.write "#{params} started\n"

        container.tap(&:start).attach(stdin: socket, tty: true) do |type, msg|
          socket.write(msg)
        end

        socket.write "returned\n"
      ensure
        socket.close
      end
    end
  end

  # -- new subresources
  def create_gear
    # TODO
    response = { message: "POST /apps/:id/gears is not implemented as yet" }
    render json: response, status: 501
  end

  def create_drain
    url = params.require(:drain).require(:url)
    if !@app.drains.where(url: url).exists?
      @drain = @app.drains.create(app: @app, url: url)
      if @drain.save
        render 'drains/drain', status: 200
      else
        response = { id: "drain.save.fail",
                     message: "saving drain failed",
                     errors: @drain.errors.to_h }
        render json: response, status: 422
      end
    else
      head 409
    end
  end

  def create_domain
    url = params.require(:domain).require(:url)
    if !@app.domains.where(url: url).exists?
      @domain = @app.domains.create(app: @app, url: url)
      if @domain.save
        render 'domains/domain', status: 200
      else
        response = { id: "domain.save.fail",
                     message: "saving domain failed",
                     errors: @domain.errors.to_h }
        render json: response, status: 422
      end
    else
      response = { id: 'domain.exists',
                   message: "Domain #{params[:url]} exists" }
      render json: response, status: 409
    end
  end

  def create_release
    @release = @app.release!
    if @release.save
      render 'releases/release', status: 200
    else
      response = { id: "drain.save.fail",
                   message: "saving drain failed",
                   errors: @drain.errors.to_h }
      render json: response, status: 422
    end
  end

  # fetch scoped subresources
  def gears
    @gears = @app.gears
    render 'gears/index', status: 200
  end

  def drains
    @drains = @app.drains
    render 'drains/index', status: 200
  end

  def domains
    @domains = @app.domains
    render 'domains/index', status: 200
  end

  def releases
    @releases = @app.releases
    render 'releases/index', status: 200
  end

  def gears_restart
    @app.gears.each &:restart
    render json: { message: "gears have been restarted" }, status: 200
  end

  private def app_params
    params.require(:app)
  end

  private def find_app
    app = App.where(id: params[:id]).first
    app = App.where(name: params[:id]).first unless app
    if app
      @app = app
    else
      response = { id: "app.not_exist",
                   message: "App (id: #{params[:id]}) does not exist" }
      render json: response, status: 404
    end
  end

  private def verify_app_owner
    unless @app.user == current_user
      response = { id: "app.not_owner",
                   message: "App (id: #{params[:id]}) does not belong to the current user" }
      render json: response, status: 401
    end
  end
end