diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1fb9ef5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tmp/* diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..990f5ce --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'http://rubygems.org' + +# Specify your gem's dependencies in wordmove.gemspec +gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..67e37ff --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,64 @@ +PATH + remote: . + specs: + wordmove (0.0.1) + activesupport (~> 3.0.0) + escape + hashie + i18n + net-scp + net-ssh + paint + rake + thor + +GEM + remote: http://rubygems.org/ + specs: + activesupport (3.0.11) + aruba (0.4.7) + childprocess (>= 0.2.2) + cucumber (>= 1.1.1) + ffi (= 1.0.9) + rspec (>= 2.7.0) + builder (3.0.0) + childprocess (0.2.3) + ffi (~> 1.0.6) + cucumber (1.1.3) + builder (>= 2.1.2) + diff-lcs (>= 1.1.2) + gherkin (~> 2.6.7) + json (>= 1.4.6) + term-ansicolor (>= 1.0.6) + diff-lcs (1.1.3) + escape (0.0.4) + ffi (1.0.9) + gherkin (2.6.8) + json (>= 1.4.6) + hashie (1.2.0) + i18n (0.6.0) + json (1.6.2) + net-scp (1.0.4) + net-ssh (>= 1.99.1) + net-ssh (2.2.1) + paint (0.8.3) + rake (0.9.2.2) + rspec (2.7.0) + rspec-core (~> 2.7.0) + rspec-expectations (~> 2.7.0) + rspec-mocks (~> 2.7.0) + rspec-core (2.7.1) + rspec-expectations (2.7.0) + diff-lcs (~> 1.1.2) + rspec-mocks (2.7.0) + term-ansicolor (1.0.7) + thor (0.14.6) + +PLATFORMS + ruby + +DEPENDENCIES + aruba + cucumber + rspec + wordmove! diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..f57ae68 --- /dev/null +++ b/Rakefile @@ -0,0 +1,2 @@ +#!/usr/bin/env rake +require "bundler/gem_tasks" diff --git a/bin/wordmove b/bin/wordmove new file mode 100755 index 0000000..4e1c648 --- /dev/null +++ b/bin/wordmove @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require 'wordmove/cli' +Wordmove::CLI.start diff --git a/features/generator.feature b/features/generator.feature new file mode 100644 index 0000000..6379110 --- /dev/null +++ b/features/generator.feature @@ -0,0 +1,30 @@ +Feature: Generating Movefile + In order to configure Wordmove + As a WP developer + I want Wordmove to generate me a Wordmove skeleton file + + Scenario: Wordmove creation + When I run "wordmove init" + Then the following files should exist: + | Movefile | + Then the file "Movefile" should contain: + """ + local: + vhost: "http://vhost.local" + wordpress_path: "~/dev/sites/your_site" + database: + username: "username" + password: "password" + host: "host" + remote: + vhost: "http://remote.com" + wordpress_path: "/var/www/your_site" + database: + username: "username" + password: "password" + host: "host" + ssh: + username: "username" + password: "password" + host: "host" + """ diff --git a/features/pull.feature b/features/pull.feature new file mode 100644 index 0000000..e69de29 diff --git a/features/push.feature b/features/push.feature new file mode 100644 index 0000000..e69de29 diff --git a/features/step_definitions/aruba_ext_steps.rb b/features/step_definitions/aruba_ext_steps.rb new file mode 100644 index 0000000..c2336c5 --- /dev/null +++ b/features/step_definitions/aruba_ext_steps.rb @@ -0,0 +1,3 @@ +Then /^the file "([^"]*)" should contain:$/ do |file, content| + check_file_content(file, content, true) +end diff --git a/features/support/setup.rb b/features/support/setup.rb new file mode 100644 index 0000000..11ca402 --- /dev/null +++ b/features/support/setup.rb @@ -0,0 +1,2 @@ +require 'aruba/cucumber' + diff --git a/lib/wordmove.rb b/lib/wordmove.rb new file mode 100644 index 0000000..4427aa0 --- /dev/null +++ b/lib/wordmove.rb @@ -0,0 +1,4 @@ +require "wordmove/version" + +module Wordmove +end diff --git a/lib/wordmove/cli.rb b/lib/wordmove/cli.rb new file mode 100644 index 0000000..91c041c --- /dev/null +++ b/lib/wordmove/cli.rb @@ -0,0 +1,36 @@ +require 'thor' +require 'wordmove/generators/movefile' +require 'wordmove/deployer' + +module Wordmove + class CLI < Thor + + desc "init", "Generates a brand new Movefile" + def init + Wordmove::Generators::Movefile.start + end + + desc "pull", "Pulls Wordpress data from remote host to the local machine" + method_option :skip_db, :aliases => "-d", :type => :boolean + method_option :skip_uploads, :aliases => "-u", :type => :boolean + method_option :skip_themes, :aliases => "-t", :type => :boolean + method_option :skip_plugins, :aliases => "-p", :type => :boolean + method_option :config, :aliases => "-c" + def pull + deployer = Wordmove::Deployer.new(options) + deployer.pull + end + + desc "push", "Push Wordpress data to remote host from local machine" + method_option :skip_db, :aliases => "-d", :type => :boolean + method_option :skip_uploads, :aliases => "-u", :type => :boolean + method_option :skip_themes, :aliases => "-t", :type => :boolean + method_option :skip_plugins, :aliases => "-p", :type => :boolean + method_option :config, :aliases => "-c" + def push + deployer = Wordmove::Deployer.new(options) + deployer.push + end + + end +end diff --git a/lib/wordmove/deployer.rb b/lib/wordmove/deployer.rb new file mode 100644 index 0000000..47fe50b --- /dev/null +++ b/lib/wordmove/deployer.rb @@ -0,0 +1,152 @@ +require 'active_support/core_ext' +require 'hashie' +require 'paint/pa' +require 'escape' +require 'net/ssh' +require 'net/scp' + +require 'wordmove/hosts/local_host' +require 'wordmove/hosts/remote_host' + +module Wordmove + + class Deployer + + def initialize(options = {}) + @options = Hashie::Mash.new(options) + end + + def push + %w(db uploads themes plugins).each do |step| + unless @options["skip_#{step}".to_s] + pa "Pushing #{step.titleize}...", :cyan + send "push_#{step}" + end + end + end + + def pull + %w(db uploads themes plugins).each do |step| + unless @options["skip_#{step}".to_s] + pa "Pushing #{step.titleize}...", :cyan + send "pull_#{step}" + end + end + end + + private + + def push_db + local_mysql_dump_path = local_wpcontent_path("database_dump.sql") + remote_mysql_dump_path = remote_wpcontent_path("database_dump.sql") + + locally do |host| + host.run "mysqldump", "--host=#{config.local.database.host}", "--user=#{config.local.database.username}", "--password=#{config.local.database.password}", config.local.database.name, :stdout => local_mysql_dump_path + File.open(local_mysql_dump_path, 'a') do |file| + file.write "UPDATE wp_options SET option_value=\"#{config.remote.vhost}\" WHERE option_name=\"siteurl\" OR option_name=\"home\";\n" + end + end + + remotely do |host| + host.download_file local_mysql_dump_path, remote_mysql_dump_path + host.run "mysql", "--user=#{config.remote.database.username}", "--password=#{config.remote.database.password}", "--host=#{config.remote.database.host}", "--database=#{config.remote.database.name}", :stdin => remote_mysql_dump_path + host.run "rm", remote_mysql_dump_path + end + + locally do |host| + host.run "rm", local_mysql_dump_path + end + end + + def push_uploads + remotely do |host| + host.download_dir local_wpcontent_path("uploads"), remote_wpcontent_path("uploads") + end + end + + def push_themes + remotely do |host| + host.download_dir local_wpcontent_path("themes"), remote_wpcontent_path("themes") + end + end + + def push_plugins + remotely do |host| + host.download_dir local_wpcontent_path("plugins"), remote_wpcontent_path("plugins") + end + end + + def pull_db + local_mysql_dump_path = local_wpcontent_path("database_dump.sql") + remote_mysql_dump_path = remote_wpcontent_path("database_dump.sql") + + remotely do |host| + host.run "mysqldump", "--host=#{config.remote.database.host}", "--user=#{config.remote.database.username}", "--password=#{config.remote.database.password}", config.remote.database.name, :stdout => remote_mysql_dump_path + host.upload_file remote_mysql_dump_path, local_mysql_dump_path + end + + locally do |host| + File.open(local_mysql_dump_path, 'a') do |file| + file.write "UPDATE wp_options SET option_value=\"#{config.local.vhost}\" WHERE option_name=\"siteurl\" OR option_name=\"home\";\n" + end + host.run "mysql", "--user=#{config.local.database.username}", "--password=#{config.local.database.password}", "--host=#{config.local.database.host}", "--database=#{config.local.database.name}", :stdin => local_mysql_dump_path + host.run "rm", local_mysql_dump_path + end + + remotely do |host| + host.run "rm", remote_mysql_dump_path + end + + end + + def pull_uploads + remotely do |host| + host.upload_dir remote_wpcontent_path("uploads"), local_wpcontent_path("uploads") + end + end + + def pull_themes + remotely do |host| + host.upload_dir remote_wpcontent_path("themes"), local_wpcontent_path("themes") + end + end + + def pull_plugins + remotely do |host| + host.upload_dir remote_wpcontent_path("plugins"), local_wpcontent_path("plugins") + end + end + + def config + if @config.blank? + config_path = @options[:config] || "Movefile" + unless File.exists? config_path + raise Thor::Error, "Could not find a valid Movefile" + end + @config = Hashie::Mash.new(YAML::load(File.open(config_path))) + end + @config + end + + def local_wpcontent_path(*args) + File.join(config.local.wordpress_path, "wp-content", *args) + end + + def remote_wpcontent_path(*args) + File.join(config.remote.wordpress_path, "wp-content", *args) + end + + def locally + host = LocalHost.new(config.local) + yield host + host.close + end + + def remotely + host = RemoteHost.new(config.remote) + yield host + host.close + end + + end +end diff --git a/lib/wordmove/generators/Movefile b/lib/wordmove/generators/Movefile new file mode 100644 index 0000000..96b608e --- /dev/null +++ b/lib/wordmove/generators/Movefile @@ -0,0 +1,25 @@ +local: + vhost: "http://vhost.local" + wordpress_path: "~/dev/sites/your_site" + database: + name: "database_name" + username: "username" + password: "password" + host: "host" +remote: + vhost: "http://remote.com" + wordpress_path: "/var/www/your_site" + exclude: + - .git + - .DS_Store + - .sass-cache + database: + name: "database_name" + username: "username" + password: "password" + host: "host" + ssh: + username: "username" + password: "password" + host: "host" + diff --git a/lib/wordmove/generators/movefile.rb b/lib/wordmove/generators/movefile.rb new file mode 100644 index 0000000..1de9d77 --- /dev/null +++ b/lib/wordmove/generators/movefile.rb @@ -0,0 +1,18 @@ +require 'thor/group' + +module Wordmove + module Generators + class Movefile < Thor::Group + include Thor::Actions + + def self.source_root + File.dirname(__FILE__) + end + + def copy_movefile + template "Movefile" + end + + end + end +end diff --git a/lib/wordmove/hosts/local_host.rb b/lib/wordmove/hosts/local_host.rb new file mode 100644 index 0000000..b6afa1a --- /dev/null +++ b/lib/wordmove/hosts/local_host.rb @@ -0,0 +1,36 @@ +module Wordmove + class LocalHost + + attr_reader :options + + def initialize(options = {}) + @options = Hashie::Mash.new(options) + end + + def run(*args) + command = shell_command(*args) + pa "Executing locally #{command}", :green, :bright + unless system(command) + raise Thor::Error, "Error executing \"#{command}\"" + end + end + + def close + end + + protected + + def shell_command(*args) + options = args.extract_options! + command = Escape.shell_command(args) + if options[:stdin] + command += " < #{options[:stdin]}" + end + if options[:stdout] + command += " > #{options[:stdout]}" + end + command + end + + end +end diff --git a/lib/wordmove/hosts/remote_host.rb b/lib/wordmove/hosts/remote_host.rb new file mode 100644 index 0000000..f0c1762 --- /dev/null +++ b/lib/wordmove/hosts/remote_host.rb @@ -0,0 +1,58 @@ +module Wordmove + class RemoteHost < LocalHost + + alias :locally_run :run + + def initialize(options = {}) + super + pa "Connecting to #{options.ssh.host}...", :green, :bright + @session = Net::SSH.start(options.ssh.host, options.ssh.username, :password => options.ssh.password) + end + + def close + @session.close + end + + def upload_file(source_file, destination_file) + pa "Copying remote #{source_file} to #{destination_file}...", :green, :bright + Net::SCP.download!(options.ssh.host, options.ssh.username, source_file, destination_file, :password => options.ssh.password) + end + + def download_file(source_file, destination_file) + pa "Copying local #{source_file} to #{destination_file}...", :green, :bright + Net::SCP.upload!(options.ssh.host, options.ssh.username, source_file, destination_file, :password => options.ssh.password) + end + + def download_dir(source_dir, destination_dir) + rsync "#{source_dir}/", "#{options.ssh.username}@#{options.ssh.host}:#{destination_dir}" + end + + def upload_dir(source_dir, destination_dir) + rsync "#{options.ssh.username}@#{options.ssh.host}:#{source_dir}/", destination_dir + end + + def run(*args) + command = shell_command(*args) + pa "Executing remotely #{command}", :green, :bright + @session.exec!(command) + end + + private + + def rsync(source_dir, destination_dir) + password_file = Tempfile.new('rsync_password') + password_file.write(options.ssh.password) + password_file.close + + exclude_file = Tempfile.new('exclude') + exclude_file.write(options.exclude.join("\n")) + exclude_file.close + + locally_run "rsync", "-avz", "--password-file=#{password_file.path}", "--exclude-from=#{exclude_file.path}", "--delete", source_dir, destination_dir + + password_file.unlink + exclude_file.unlink + end + + end +end diff --git a/lib/wordmove/version.rb b/lib/wordmove/version.rb new file mode 100644 index 0000000..24f0999 --- /dev/null +++ b/lib/wordmove/version.rb @@ -0,0 +1,3 @@ +module Wordmove + VERSION = "0.0.1" +end diff --git a/spec/foodie_spec.rb b/spec/foodie_spec.rb new file mode 100644 index 0000000..c3670a9 --- /dev/null +++ b/spec/foodie_spec.rb @@ -0,0 +1,9 @@ +describe Foodie::Food do + it "broccoli is gross" do + Foodie::Food.portray("Broccoli").should eql("Gross!") + end + + it "anything else is delicious" do + Foodie::Food.portray("Not Broccoli").should eql("Delicious!") + end +end diff --git a/wordmove.gemspec b/wordmove.gemspec new file mode 100644 index 0000000..8a02f6c --- /dev/null +++ b/wordmove.gemspec @@ -0,0 +1,31 @@ +# -*- encoding: utf-8 -*- +require File.expand_path('../lib/wordmove/version', __FILE__) + +Gem::Specification.new do |gem| + gem.authors = ["Stefano Verna"] + gem.email = ["stefano.verna@welaika.com"] + gem.description = %q{TODO: Write a gem description} + gem.summary = %q{TODO: Write a gem summary} + gem.homepage = "" + + gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + gem.files = `git ls-files`.split("\n") + gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + gem.name = "wordmove" + gem.require_paths = ["lib"] + gem.version = Wordmove::VERSION + + gem.add_dependency 'paint' + gem.add_dependency 'escape' + gem.add_dependency 'rake' + gem.add_dependency 'net-ssh' + gem.add_dependency 'net-scp' + gem.add_dependency "thor" + gem.add_dependency "activesupport", "~> 3.0.0" + gem.add_dependency "i18n" + gem.add_dependency "hashie" + + gem.add_development_dependency "rspec" + gem.add_development_dependency "cucumber" + gem.add_development_dependency "aruba" +end