Convox Installer

A Ruby gem that makes it easier to build a Convox installation script. This is like Chef/Ansible/Terraform for your initial Convox setup.

NOTE: This software is an alpha version

Please note that the code quality is not very good, and the test coverage needs to be improved. However, I've successfully set up a number of test and production deployments using this gem, and everything seems to work very well. The library also facilitates idempotency and crash-resistance, so you can easily re-run your installation script if something goes wrong.

Features

  • Idempotent. If this script crashes, you can restart it and it will pick up where it left off. Every step looks up the existing state, and only makes a change if things are not yet set up (or out of sync).
  • Ensures that the convox and aws CLI tools are installed
  • Wraps the convox CLI and parses JSON output from API calls
  • Add n Docker Repository (e.g. ECR registry)
  • Set up an S3 bucket with an optional CORS policy

Introduction

Convox is an awesome open source PaaS, which is like Heroku for your own AWS account. convox/rack is completely open source and free to use, but you can also sign up for a free or paid account to use the hosted service on convox.com.

convox_installer is a Ruby gem that makes it much easier to build an installation script for convox/rack (the open source PaaS). The Convox CLI is awesome, but it's missing a nice way to script a full deployment. I originally wrote a bash script that made API calls and used jq and sed, but this was very error-prone and it did not have good cross-platform support.

I've rewritten this installation script in Ruby, which provides very good cross-platform support, and also allows me to write tests.

Usage

Create a new Ruby file (e.g. install.rb), and use bundler/inline to install and require the convox_installer gem. Your install script should start like this:

#!/usr/bin/env ruby
require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'convox_installer'
end

require "convox_installer"
include ConvoxInstaller

Including the include ConvoxInstaller gives you some Ruby methods that you can call to construct an installation workflow. See the "ConvoxInstaller DSL" section below.

You should create a new git repo for your own installation script, and then use the provided classes and methods to build your own installation workflow. You must also include a convox.yml (or a convox.example.yml).

You can see a complete example in examples/full_installation.rb.

Config

Config is loaded from ENV vars, or from saved JSON data at ./.installer_config. The script will save all of the user's responses into ./.installer_config (in the current directory).

Customize the Config Prompts

You can set your own config prompts in your own installation script, by setting a @prompts instance variable. You can extend the default config prompts like this:

@prompts = ConvoxInstaller::Config::DEFAULT_PROMPTS + [
  {
    section: "Docker Authentication",
    info: "You should have received authentication details for the Docker Registry\n" \
    "via email. If not, please contact [email protected]",
  },
  {
    key: :docker_registry_url,
    title: "Docker Registry URL",
    value: "1234567890.dkr.ecr.us-east-1.amazonaws.com",
  },
  {
    key: :docker_registry_username,
    title: "Docker Registry Username",
  },
  {
    key: :docker_registry_password,
    title: "Docker Registry Password",
  }
]

Prompt API:

The @prompts variable must be an array of hashes. There are two kinds of hashes:

Section Heading

Shows a heading and optional details.

{
  section: "The heading for this config section",
  info: "Description about this config section"
}

Config Prompt

  • A config prompt with a default value:
{
  key: :config_key_name,
  title: "Title to show in the user prompt / config summary",
  prompt: "Question to show the user",
  default: "default value",
}
  • Set a value from a Proc, and don't prompt the user:
  {
    key: :config_key_name,
    title: "Title to show in the config summary",
    value: -> () { "string-with-random-suffix-#{SecureRandom.hex(4)}" },
  }
  • Set a value, and hide this setting from the user (even in the summary):
  {
    key: :config_key_name,
    value: "Config Value",
    hidden: true,
  },

ConvoxInstaller DSL

ensure_requirements!

Makes sure that the convox and aws CLI tools are installed on this system. If not, shows installation instructions and exits.

prompt_for_config

Loads config from ENV vars, or from saved config at ./.installer_config. If any config settings are missing, it prompts the user for input. Finally, it shows a summary of the config, and asks the user if they want to proceed with the installation. If the user enters y (or yes), the prompt_for_config method completes. If they enter n (or no), we loop over every setting and let them press "enter" to keep the current value, or provide a new value to correct any mistakes.

backup_convox_host_and_rack

If there are any existing files at ~/.convox/host or ~/.convox/rack, this method moves these to ~/.convox/host.bak and ~/.convox/rack.bak.

install_convox

  • Required Config: aws_region, aws_access_key_id, aws_secret_access_key, stack_name, instance_type

Runs convox rack install .... Has some validations to ensure that all required settings are present.

validate_convox_auth_and_set_host!

After running install_convox, call this method to ensure that the the ~/.convox/auth file has been updated with the correct details (checks the rack name and AWS region.) Then it sets the rack host in ~/.convox/host (if not already set.)

validate_convox_rack!

Calls convox api get /system to get the Rack details, then makes sure that everything is correct.

convox_rack_data

Returns a Ruby hash with all convox rack data.

create_convox_app!

  • Required Config: convox_app_name

Checks if the app already exists. If not, calls convox apps create ... --wait to create a new app. Then waits for the app to be ready. (Avoids an occasional race condition.)

set_default_app_for_directory!

Writes the app name into ./.convox/app (in the current directory.) The convox CLI reads this file, so you don't need to specify the --app flag for future commands.

create_s3_bucket!

  • Required Config: s3_bucket_name

Creates an S3 bucket from the :s3_bucket_name config setting. This is not a default setting, so you can add something like this to your custom @prompts:

  {
    key: :s3_bucket_name,
    title: "S3 Bucket for uploads",
    value: -> () { "yourapp-uploads-#{SecureRandom.hex(4)}" },
  }

The :value Proc will generate a bucket name with a random suffix. (Avoids conflicts when you are setting up multiple deployments for your app.)

create_s3_bucket! will also call set_s3_bucket_cors_policy automatically, so you don't need to call this manually.

set_s3_bucket_cors_policy

  • Required Config: s3_bucket_name

Set up a CORS policy for your S3 bucket. (:s3_bucket_name)

Note: If the :s3_bucket_cors_policy setting is not provided, then this method does nothing.

You should set :s3_bucket_cors_policy to a JSON string. Here's how I set this up in my own install.rb script:

S3_BUCKET_CORS_POLICY = <<-JSON
{
  "CORSRules": [
    {
      "AllowedOrigins": ["*"],
      "AllowedHeaders": ["Authorization", "cache-control", "x-requested-with"],
      "AllowedMethods": ["PUT", "POST", "GET"],
      "MaxAgeSeconds": 3000
    }
  ]
}
JSON

@prompts = [
  {
    key: :s3_bucket_cors_policy,
    value: S3_BUCKET_CORS_POLICY,
    hidden: true,
  }
]

s3_bucket_details

  • Required Config: s3_bucket_name

Get the S3 bucket details for s3_bucket_name. Parses the URL and returns a hash:

{
  access_key_id: "AWS Access Key ID",
  secret_access_key: "AWS Secret Access Key",
  name: "Full S3 Bucket Name (includes the rack/app)",
}

I use these S3 bucket details to set env variables for my app. (convox env set ...)

add_docker_registry!

  • Required Config: docker_registry_url, docker_registry_username, docker_registry_password

Checks the list of registries to see if docker_registry_url has already been added. If not, runs convox registries add ... to add a new Docker registry (e.g. Docker Hub, ECR).

default_service_domain_name

  • Required Config: convox_app_name, default_service

Parses the rack router ELB name and region, and returns the default convox.site domain for your default service. (You can visit this URL in the browser to access your app.)

Example: myapp-web.rackname-route-abcdfe123456-123456789.us-west-2.convox.site

Set a default service in your config prompts (e.g. web):

@prompts = [
  # ...
  {
    key: :default_service,
    title: "Default Convox Service (for domain)",
    value: "web",
    hidden: true,
  }
]

(This hidden setting isn't visible to the user.)

run_convox_command!(cmd)

Runs a convox CLI command, and shows all output in the terminal. Crashes the script with an error if the convox command has a non-zero exit code.

If you want to run convox env set MYVAR=value, then you would call:

run_convox_command! 'env set MYVAR=value'

License

MIT