Automating WP Engine’s User Portal with Ruby Script

There is a Ruby gem called Mechanize which is a powerful library for automating web interactions. While playing around with it over the course of a weekend I ended up making the following Ruby script can be run over the command line to login to WP Engine’s web user portal (my.wpengine.com) and do various tasks.

#!/usr/bin/env ruby

#
#     Access backend of WP Engine through command line ruby script
#
#     ruby wpengine_portal.rb [install or account name] command
#

require 'rubygems'
require 'mechanize'

# Used for command line input (Two-Factor Auth)
require "highline/import"

# Load command line arguments
@install = ARGV[0]
@command = ARGV[1] 

@agent = Mechanize.new

# Commands which can be passed into install/account

    def storage_count
        new_page = @agent.get('https://my.wpengine.com/installs/' + @install)
        new_page.search("table.install-detail tr:nth-child(3) .usage-value").text()
    end

    # Download usage for selected install
    def download_usage
        @agent.pluggable_parser.default = Mechanize::Download
        @agent.get('https://my.wpengine.com/installs/' + @install + '/usage_stats').save(@install + '_usage_stats.csv')
    end

    # Outputs all installs on selected server
    def installs_on_account
        new_page = @agent.get('https://my.wpengine.com/accounts/' + @install)
        puts new_page.search("td.install-name a").count
        new_page.search("td.install-name a").each { |link| puts link.text }
    end

    # Download usage for all sites
    def download_usage_for_all_installs
        new_page = @agent.get('https://my.wpengine.com/accounts/' + @install)
        new_page = @agent.get('https://my.wpengine.com/sites/')
        @agent.pluggable_parser.default = Mechanize::Download
        puts new_page.search("div[data-modal-path*=installs] a[href*=install],div[data-modal-path*=sites] > div:nth-child(3) a[href*=install]").count
        new_page.search("div[data-modal-path*=installs] a[href*=install],div[data-modal-path*=sites] > div:nth-child(3) a[href*=install]").each { |link| 
            puts 'Downloading usage_stats/' + link.text + '_usage_stats.csv'
            @agent.get('https://my.wpengine.com/installs/' + link.text + '/usage_stats').save!('usage_stats/' +  link.text + '_usage_stats.csv')
        }
    end

# Load cookies if they exist
cookie_file = File.expand_path("cookies.yaml",File.dirname(__FILE__))
if File.exist?(cookie_file)
  @agent.cookie_jar.load(cookie_file)
end

# Access the dashboard
page = @agent.get('https://my.wpengine.com/')

# Prompt for login credentials if login form found
if page.form and page.form.action == "/users/login" and page.form.field_with(:name => 'user[email]')

    # Command line prompt for email and password
    email = ask "Enter Email: "
    password = ask("Enter Password:  ") { |q| q.echo = "x" }

    puts "Logging into https://my.wpengine.com/"
    wpengine_form = page.form
    wpengine_form.field_with(:name => 'user[email]').value = email
    page = @agent.submit(wpengine_form)
    wpengine_form_password = page.form
    wpengine_form_password.field_with(:name => 'user[password]').value = password
    page = @agent.submit(wpengine_form_password)

end

# Prompt for Two-Factor Authentication is needed
if page.form and page.form.action == "/users/login" and page.form.field_with(:name => 'user[otp_attempt]')

    # Command line prompt for two factor code
    two_factor = ask "Two-Factor Authentication code: "

    wpengine_form = page.form
    wpengine_form.field_with(:name => 'user[otp_attempt]').value = two_factor
    wpengine_form.checkbox_with(:name => 'user[remember_2fa_device]').check
    page = @agent.submit(wpengine_form)

end

new_page = @agent.get('https://my.wpengine.com/sites/')

# Count number of @installs displayed on the dashboard
@install_count = new_page.search("div[data-modal-path*=installs] a[href*=install],div[data-modal-path*=sites] > div:nth-child(3) a[href*=install]").count

# Assume we are logged in successfully if any installs are found
if @install_count > 1 

    # Store the cookie as a session to skip login on next script run
    stringio = StringIO.new
    @agent.cookie_jar.save(cookie_file, session: true)

    # Output and run custom functions here
    puts send(@command)

end

The command line format goes like this ruby wpengine_portal.rb [install or account name] [command]. There are three commands which it can take.

  • storage_count –  output storage for install
  • download_usage – downloads usage stats for install
  • installs_on_account – list all installs on a particular account

Mechanize can store and reuse sessions

After the initial authentication process Mechanize saves the session to a .yaml file which can be loaded up on future requests. Below you see a quick demo of just that. Notice the second time I run the same command it doesn’t need to sign in as it’s already logged in.

Recording-182.gif

Extendable but no javascript support

The three functions I made are really just a sample of the power of Mechanize. It can be extended to do lots more. Mechanize is like a headless web browser for developers without javascript support.

Mechanize can only see and interact with html not javascript. That means any part of the website which is using javascript to generate html will be invisible to Mechanize. If javascript is needed you most likely will need to use another library/language like PhantomJS.

Automate to save yourself time

There is also a legal consideration. While I’m not one to ask legal advice for, using automation tools to authenticate through a service you pay for is a grey area. In general I would say if by automating you save yourself time doing something which you were going to have to manually do, you should. If, however, through automation you create some new functionality or service you should really have a conversation with the company your employing automation on.