From 6180fa23e821b7629402192f51e9611aa12314dd Mon Sep 17 00:00:00 2001 From: Pete Keen Date: Fri, 17 Aug 2012 19:58:46 -0700 Subject: [PATCH] Add some initial buildpack and application commands --- lib/dokuen.rb | 1 + lib/dokuen/application.rb | 41 +++++++++++++++ lib/dokuen/cli.rb | 107 +++++++++++++++++++++++++++++++------- lib/dokuen/remote.rb | 57 ++++++++++++++++++-- 4 files changed, 184 insertions(+), 22 deletions(-) create mode 100644 lib/dokuen/application.rb diff --git a/lib/dokuen.rb b/lib/dokuen.rb index c9dd165..a1cfbda 100644 --- a/lib/dokuen.rb +++ b/lib/dokuen.rb @@ -15,5 +15,6 @@ def self.template(name, bind) require "dokuen/config" require "dokuen/remote" +require "dokuen/application" require "dokuen/cli" diff --git a/lib/dokuen/application.rb b/lib/dokuen/application.rb new file mode 100644 index 0000000..dcb0577 --- /dev/null +++ b/lib/dokuen/application.rb @@ -0,0 +1,41 @@ +class Dokuen::Application + + attr_reader :name, :remote + + def initialize(remote, name) + @remote = remote + @name = name + end + + def run(*args) + remote.run(*args) + end + + def sudo(*args) + remote.sudo(*args) + end + + def create! + return if remote.application_exists?(name) + + remote.create_user(name) + dirs = [ + 'releases', + 'env', + 'logs', + 'build' + ] + dirs.each do |dir| + sudo("mkdir -p #{remote.path}/apps/#{name}/#{dir}") + end + sudo("chown -R #{name}.#{name} #{remote.path}/apps/#{name}") + end + + def destroy! + return unless remote.application_exists?(name) + + sudo("rm -rf #{remote.path}/apps/#{name}") + sudo("userdel #{name}") + end + +end diff --git a/lib/dokuen/cli.rb b/lib/dokuen/cli.rb index 0213546..4b52e8c 100644 --- a/lib/dokuen/cli.rb +++ b/lib/dokuen/cli.rb @@ -8,15 +8,33 @@ class Dokuen::CLI < Thor class_option :config, :type => :string, :desc => "Config file" - class Remote < Thor + class SubCommand < Thor - namespace :remote + no_tasks do + + def initialize(*args) + super(*args) + @config = Dokuen::Config.new(options[:config] || "~/.dokuen") + end + + def verify_remote(remote, create_remote=true) + if @config[:remotes][remote].nil? + raise Thor::Error.new("#{remote} is not a known remote") + end + @remote = Dokuen::Remote.new(@config[:remotes][remote]) if create_remote + end - def initialize(*args) - super(*args) - @config = Dokuen::Config.new(options[:config] || "~/.dokuen") + def self.banner(task, namespace = true, subcommand = false) + "#{basename} #{task.formatted_usage(self, true, subcommand)}" + end end + end + + class Remote < SubCommand + + namespace :remote + desc "add NAME SPEC", "Add a remote named NAME with spec SPEC (ex: user@hostname:/path/to/dokuen)" def add(name, spec) @config[:remotes][name] = spec @@ -26,34 +44,85 @@ def add(name, spec) desc "remove NAME", "Remote the named remote from dokuen config" def remove(name) - if @config[:remotes][name].nil? - raise Thor::Error.new("#{name} is not a known remote") - end + verify_remote(name, false) @config[:remotes].delete(name) @config.write_file say "Removed #{name} from #{@config.filename}" end - desc "setup NAME", "Setup Dokuen on the named remote" - def setup(name) - if @config[:remotes][name].nil? - raise Thor::Error.new("#{name} is not a known remote") - end + desc "prepare NAME", "Setup Dokuen on the named remote" + def prepare(name) + verify_remote(name) - say "Setting up dokuen on #{name}" + say "Preparing #{name} for dokuen" - remote = Dokuen::Remote.new(@config[:remotes][name]) - remote.setup! + @remote.prepare! end - def self.banner(task, namespace = true, subcommand = false) - "#{basename} #{task.formatted_usage(self, true, subcommand)}" - end + end + + class Buildpack < SubCommand + namespace :buildpack + + desc "add REMOTE URL", "Add a buildpack to the standard set" + def add(remote, url) + verify_remote(remote) + + say "Cloning buildpack from #{url} onto #{remote}" + + @remote.clone_buildpack(url) + end + desc "remove REMOTE NAME", "Remove buildpack from remote" + def remove(remote, name) + verify_remote(remote) + + say "Removing buildpack #{name} from remote #{remote}" + + @remote.remove_buildpack(name) + end + + end + + class Application < SubCommand + + namespace :app + + desc "create REMOTE NAME", "Create an application" + def create(remote, name) + verify_remote(remote) + + say "Creating application #{name} on #{remote}" + + if @remote.application_exists? name + raise "Application #{name} already exists on remote #{remote}" + end + app = Dokuen::Application.new(@remote, name) + app.create! + end + + desc "destroy REMOTE NAME", "Destroy an application" + def destroy(remote, name) + verify_remote(remote) + say "Destroying application #{name} on #{remote}" + + raise "Application does not exist" unless @remote.application_exists?(name) + + say "THIS IS PERMANENT" + say "Type '#{name}' at the prompt below to confirm" + + confirm = ask "Confirm: " + raise "Confirmation invalid!" unless confirm == name + + app = Dokuen::Application.new(@remote, name) + app.destroy! + end end register(Remote, 'remote', 'remote ', 'Manipulate Dokuen remotes') + register(Buildpack, 'buildpack', 'buildpack ', 'Manipulate Dokuen buildpacks') + register(Application, 'app', 'app ', 'Manipulate Dokuen applications') end diff --git a/lib/dokuen/remote.rb b/lib/dokuen/remote.rb index 767d278..8c9a5c1 100644 --- a/lib/dokuen/remote.rb +++ b/lib/dokuen/remote.rb @@ -1,17 +1,68 @@ require 'capistrano' +require 'fileutils' class Dokuen::Remote + + attr_reader :user, :server_name, :path + def initialize(spec) @remote_spec = spec @server_name, @path = spec.split(/:/, 2) - + @user, rest = @server_name.split(/@/, 2) + @cap = Capistrano::Configuration.new @cap.logger.level = Capistrano::Logger::TRACE @cap.server(@server_name, :dokuen) end - def setup! - @cap.run("hostname") + def prepare! + mkdirs + install_foreman end + + def run(*args) + @cap.run(*args) + end + + def sudo(*args) + @cap.sudo(*args) + end + + def mkdirs + dirs = [ + 'apps', + 'env', + 'nginx', + 'buildpacks', + ] + + dirs.each do |dir| + full_path = File.join(path, dir) + sudo("mkdir -p #{full_path}") + sudo("chown #{user} #{full_path}") + end + end + + def install_foreman + sudo("apt-get -y install ruby1.9.1") + sudo("gem install foreman -v 0.55.0") + end + + def clone_buildpack(url) + run("cd #{path}/buildpacks && git clone #{url}") + end + + def remove_buildpack(name) + run("rm -rf #{path}/buildpacks/#{name}") + end + + def application_exists?(name) + @cap.capture("([ -d #{path}/apps/#{name} ] && echo #{name}) || echo '' ") != "" + end + + def create_user(name) + sudo("useradd --home #{path}/apps/#{name} --shell /usr/bin/false #{name}") + end + end