aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristopher Baines <mail@cbaines.net>2019-05-27 21:35:53 +0100
committerChristopher Baines <mail@cbaines.net>2019-05-30 08:33:03 +0100
commit859302774c4d719cf949f419d0efcfd8cbad6ca2 (patch)
treea83c053f6a0e95554ea30784caf2837eb6837179
parent1acde9a21a50f4227930afb5b6b9eda1cace54b8 (diff)
downloadgovuk-mini-environment-admin-859302774c4d719cf949f419d0efcfd8cbad6ca2.tar
govuk-mini-environment-admin-859302774c4d719cf949f419d0efcfd8cbad6ca2.tar.gz
Add a new AWS backend using Amazon Machine Images
The existing AWS backend uses system containers backed on to the AWS hosted NFS service (EFS). This has some advantages, but also some disadvantages. Using the EFS service allows building a container on one instance, with the state being held on the EFS, and then launching a new instance to run the container. Using EFS also provides persistence, at least beyond individual EC2 instances. However, build performance when using EFS is poor compared with a local store without the overhead of the network latency. Additionally, the startup speed of the container running off EFS is slow compared to local storage. This backend doesn't use EFS, instead the Guix store sits on instance storage. Rather than using a system container for a Mini Environment, an Amazon Machine Image (AMI) is built instead. The fast local storage makes builds faster, and using EBS storage for the Mini Envirnoments as well as not having the overhead of starting Ubuntu, then starting the container also makes startup faster.
-rw-r--r--app/controllers/backends/terraform_aws_using_ami_controller.rb118
-rw-r--r--app/controllers/govuk_guix/revisions_controller.rb3
-rw-r--r--app/jobs/backends/terraform_aws_using_ami_job.rb46
-rw-r--r--app/models/backends.rb3
-rw-r--r--app/models/backends/terraform_aws.rb4
-rw-r--r--app/models/backends/terraform_aws_using_ami.rb90
-rw-r--r--app/models/backends/terraform_aws_using_ami/backend_methods.rb146
-rw-r--r--app/models/backends/terraform_aws_using_ami/mini_environment_methods.rb117
-rw-r--r--app/services/govuk_guix/build_mini_environment.rb12
-rw-r--r--app/views/backends/terraform_aws_using_ami/new.html.erb192
-rw-r--r--app/views/backends/terraform_aws_using_ami/show.html.erb362
-rw-r--r--config/routes.rb9
-rw-r--r--db/migrate/20190519091614_terraform_aws_using_ami.rb17
-rw-r--r--db/structure.sql57
-rw-r--r--terraform/aws_using_ami/backend/guix-daemon.service.tpl17
-rw-r--r--terraform/aws_using_ami/backend/main.tf437
-rw-r--r--terraform/aws_using_ami/mini_environment/main.tf114
17 files changed, 1735 insertions, 9 deletions
diff --git a/app/controllers/backends/terraform_aws_using_ami_controller.rb b/app/controllers/backends/terraform_aws_using_ami_controller.rb
new file mode 100644
index 0000000..9fbe115
--- /dev/null
+++ b/app/controllers/backends/terraform_aws_using_ami_controller.rb
@@ -0,0 +1,118 @@
+# GOV.UK Mini Environment Admin
+# Copyright © 2018, 2019 Christopher Baines <mail@cbaines.net>
+#
+# This file is part of the GOV.UK Mini Environment Admin.
+#
+# The GOV.UK Mini Environment Admin is free software: you can
+# redistribute it and/or modify it under the terms of the GNU Affero
+# General Public License as published by the Free Software Foundation,
+# either version 3 of the License, or (at your option) any later
+# version.
+#
+# The GOV.UK Mini Environment Admin is distributed in the hope that it
+# will be useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public
+# License along with the GOV.UK Mini Environment Admin. If not, see
+# <http://www.gnu.org/licenses/>.
+
+class Backends::TerraformAwsUsingAmiController < ApplicationController
+ def new
+ @backend = Backends::TerraformAws.new
+ end
+
+ def create
+ backend = Backends::TerraformAwsUsingAmi.create(create_params)
+
+ flash[:success] = "Backend #{backend.label} created"
+
+ redirect_to terraform_aws_using_ami_backend_path(backend)
+ end
+
+ def update
+ @backend = Backends::TerraformAwsUsingAmi.update(
+ params[:id],
+ update_params
+ )
+
+ flash[:success] = "Backend #{@backend.label} updated"
+
+ render :show
+ end
+
+ def show
+ @backend = Backends::TerraformAwsUsingAmi.find(params[:id])
+ end
+
+ def destroy
+ backend = Backends::TerraformAwsUsingAmi.find(params[:id])
+
+ if @backend.mini_environments.empty?
+ flash[:success] = "Backend #{backend.label} deleted"
+ backend.delete
+ else
+ flash[:error] = "Unable to delete backend, as mini environments using this backend still exist."
+ end
+
+ redirect_to setup_path
+ end
+
+ def perform_action
+ @backend = Backends::TerraformAwsUsingAmi.find(params['id'])
+
+ action = params.require(:commit)
+
+ case action
+ when 'Destroy'
+ Backends::TerraformAwsUsingAmiJob.enqueue(@backend.id, :destroy_backend)
+
+ flash[:notice] = 'Destroying the backend'
+ when 'Stop'
+ Backends::TerraformAwsUsingAmiJob.enqueue(@backend.id, :stop_backend)
+
+ flash[:notice] = 'Stopping the backend'
+ when 'Deploy'
+ Backends::TerraformAwsUsingAmiJob.enqueue(@backend.id, :deploy_backend)
+
+ flash[:notice] = 'Deploying the backend'
+ when 'Refresh state'
+ Backends::TerraformAwsUsingAmiJob.enqueue(@backend.id, :refresh_backend_state)
+
+ flash[:notice] = 'Refreshing the backend state information'
+ else
+ flash[:error] = "Unknown action #{action}"
+ end
+
+ redirect_to terraform_aws_using_ami_backend_path(@backend)
+ end
+
+ private
+
+ def create_params
+ params
+ .require(:backends_terraform_aws)
+ .permit(
+ :label,
+ :domain,
+ :aws_region,
+ :vpc_id,
+ :route_53_zone_id,
+ :aws_access_key_id,
+ :aws_secret_access_key,
+ :ssh_public_key,
+ :ssh_private_key
+ )
+ end
+
+ def update_params
+ params
+ .require(:backends_terraform_aws)
+ .permit(
+ :label,
+ :aws_access_key_id,
+ :aws_secret_access_key
+ )
+ end
+end
diff --git a/app/controllers/govuk_guix/revisions_controller.rb b/app/controllers/govuk_guix/revisions_controller.rb
index e4e1b52..1544bc1 100644
--- a/app/controllers/govuk_guix/revisions_controller.rb
+++ b/app/controllers/govuk_guix/revisions_controller.rb
@@ -40,7 +40,8 @@ class GovukGuix::RevisionsController < ApplicationController
options = {}
else
# Assume that the AWS backend is in use
- backend = Backends::TerraformAws.first
+ backend = Backends::TerraformAwsUsingAmi.first ||
+ Backends::TerraformAws.first
options = {
backend_type_and_id: backend.type_and_id
diff --git a/app/jobs/backends/terraform_aws_using_ami_job.rb b/app/jobs/backends/terraform_aws_using_ami_job.rb
new file mode 100644
index 0000000..1412e8c
--- /dev/null
+++ b/app/jobs/backends/terraform_aws_using_ami_job.rb
@@ -0,0 +1,46 @@
+# GOV.UK Mini Environment Admin
+# Copyright © 2018, 2019 Christopher Baines <mail@cbaines.net>
+#
+# This file is part of the GOV.UK Mini Environment Admin.
+#
+# The GOV.UK Mini Environment Admin is free software: you can
+# redistribute it and/or modify it under the terms of the GNU Affero
+# General Public License as published by the Free Software Foundation,
+# either version 3 of the License, or (at your option) any later
+# version.
+#
+# The GOV.UK Mini Environment Admin is distributed in the hope that it
+# will be useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public
+# License along with the GOV.UK Mini Environment Admin. If not, see
+# <http://www.gnu.org/licenses/>.
+
+class Backends::TerraformAwsUsingAmiJob < Que::Job
+ def run(terraform_aws_using_ami_backend_id, action)
+ ActiveRecord::Base.transaction do
+ @backend = Backends::TerraformAwsUsingAmi.find(
+ terraform_aws_using_ami_backend_id
+ )
+
+ @backend.send(action)
+
+ finish
+ end
+ end
+
+ def self.job_title(que_job)
+ que_job.args.last.titleize
+ end
+
+ def self.jobs(terraform_aws_using_ami_backend_id)
+ QueJob
+ .where(
+ job_class: name
+ ).where(
+ "args->>0 = '#{terraform_aws_using_ami_backend_id}'"
+ )
+ end
+end
diff --git a/app/models/backends.rb b/app/models/backends.rb
index 526ae76..2a3bceb 100644
--- a/app/models/backends.rb
+++ b/app/models/backends.rb
@@ -1,5 +1,5 @@
# GOV.UK Mini Environment Admin
-# Copyright © 2018 Christopher Baines <mail@cbaines.net>
+# Copyright © 2018, 2019 Christopher Baines <mail@cbaines.net>
#
# This file is part of the GOV.UK Mini Environment Admin.
#
@@ -26,6 +26,7 @@ module Backends
def self.classes
[
Backends::TerraformAws,
+ Backends::TerraformAwsUsingAmi,
Backends::TerraformLibvirt
]
end
diff --git a/app/models/backends/terraform_aws.rb b/app/models/backends/terraform_aws.rb
index d3d56d1..64d24ef 100644
--- a/app/models/backends/terraform_aws.rb
+++ b/app/models/backends/terraform_aws.rb
@@ -1,5 +1,5 @@
# GOV.UK Mini Environment Admin
-# Copyright © 2018 Christopher Baines <mail@cbaines.net>
+# Copyright © 2018, 2019 Christopher Baines <mail@cbaines.net>
#
# This file is part of the GOV.UK Mini Environment Admin.
#
@@ -47,7 +47,7 @@ class Backends::TerraformAws < ApplicationRecord
self.table_name = 'terraform_aws_backends'
def self.label
- 'Amazon Web Services'
+ 'Amazon Web Services (using EFS)'
end
def self.available?
diff --git a/app/models/backends/terraform_aws_using_ami.rb b/app/models/backends/terraform_aws_using_ami.rb
new file mode 100644
index 0000000..bb9f90d
--- /dev/null
+++ b/app/models/backends/terraform_aws_using_ami.rb
@@ -0,0 +1,90 @@
+# GOV.UK Mini Environment Admin
+# Copyright © 2018, 2019 Christopher Baines <mail@cbaines.net>
+#
+# This file is part of the GOV.UK Mini Environment Admin.
+#
+# The GOV.UK Mini Environment Admin is free software: you can
+# redistribute it and/or modify it under the terms of the GNU Affero
+# General Public License as published by the Free Software Foundation,
+# either version 3 of the License, or (at your option) any later
+# version.
+#
+# The GOV.UK Mini Environment Admin is distributed in the hope that it
+# will be useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public
+# License along with the GOV.UK Mini Environment Admin. If not, see
+# <http://www.gnu.org/licenses/>.
+
+# == Schema Information
+#
+# Table name: terraform_aws_backends
+#
+# id :integer not null, primary key
+# label :string
+# aws_region :string
+# aws_access_key_id :string
+# aws_secret_access_key :string
+# created_at :datetime not null
+# updated_at :datetime not null
+# domain :string
+# route_53_zone_id :string not null
+# vpc_id :string not null
+# ssh_public_key :string
+# ssh_private_key :string
+#
+
+require 'ruby_terraform'
+
+class Backends::TerraformAwsUsingAmi < ApplicationRecord
+ include MiniEnvironmentMethods
+ include BackendMethods
+
+ has_many :mini_environments, as: :backend
+
+ self.table_name = 'terraform_aws_using_ami_backends'
+
+ def self.label
+ 'Amazon Web Services (using AMIs)'
+ end
+
+ def self.available?
+ File.exist? "#{ENV['PATH'].split(':').first}/terraform-provider-aws"
+ end
+
+ def type_and_id
+ "#{self.class.name}=#{id}"
+ end
+
+ def common_terraform_variables
+ {
+ aws_access_key: aws_access_key_id,
+ aws_secret_key: aws_secret_access_key,
+ aws_region: aws_region,
+ ssh_private_key: ssh_private_key,
+ aws_route_53_zone_id: route_53_zone_id
+ }
+ end
+
+ def build_remote_host
+ RemoteHost.new(
+ 'ubuntu',
+ backend_latest_terraform_state.output_value('guix_daemon_public_dns'),
+ ssh_private_key
+ )
+ end
+
+ def terraform_state_id
+ "backend/terraform_aws_using_ami/#{id}"
+ end
+
+ def guix_public_key
+ "(entry #{File.read("/etc/guix/signing-key.pub")} (tag (guix import)))"
+ rescue Errno::ENOENT
+ # This is optional, as if it doesn't exist, it means that `guix
+ # copy` won't be used
+ ''
+ end
+end
diff --git a/app/models/backends/terraform_aws_using_ami/backend_methods.rb b/app/models/backends/terraform_aws_using_ami/backend_methods.rb
new file mode 100644
index 0000000..10bf872
--- /dev/null
+++ b/app/models/backends/terraform_aws_using_ami/backend_methods.rb
@@ -0,0 +1,146 @@
+# GOV.UK Mini Environment Admin
+# Copyright © 2018, 2019 Christopher Baines <mail@cbaines.net>
+#
+# This file is part of the GOV.UK Mini Environment Admin.
+#
+# The GOV.UK Mini Environment Admin is free software: you can
+# redistribute it and/or modify it under the terms of the GNU Affero
+# General Public License as published by the Free Software Foundation,
+# either version 3 of the License, or (at your option) any later
+# version.
+#
+# The GOV.UK Mini Environment Admin is distributed in the hope that it
+# will be useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public
+# License along with the GOV.UK Mini Environment Admin. If not, see
+# <http://www.gnu.org/licenses/>.
+
+module Backends::TerraformAwsUsingAmi::BackendMethods
+ def create_data_snapshot
+ GovukGuix::CreateDataSnapshotJob.enqueue(
+ backend_type: self.class.name,
+ backend_id: id
+ )
+ end
+
+ def backend_terraform_variables
+ public_ip_addresses = ENV[
+ 'GOVUK_MINI_ENVIRONMENT_ADMIN_PUBLIC_IP_ADDRESSES'
+ ].split(',')
+
+ raise 'missing public ip addresses' if public_ip_addresses.nil?
+
+ egress_cidr_blocks = public_ip_addresses.map { |x| "#{x}/32" }
+
+ common_terraform_variables.merge(
+ aws_vpc_id: vpc_id,
+ ssh_public_key: ssh_public_key,
+ backend_slug: label.parameterize,
+ mini_environment_admin_guix_public_key: guix_public_key,
+ mini_environment_admin_egress_cidr_blocks: egress_cidr_blocks
+ )
+ end
+
+ def deploy_backend
+ within_backend_terraform_working_directory do
+ RubyTerraform.apply(
+ vars: backend_terraform_variables,
+ auto_approve: true
+ )
+ end
+ end
+
+ def refresh_backend_state
+ within_backend_terraform_working_directory do
+ RubyTerraform.refresh(
+ vars: backend_terraform_variables
+ )
+ end
+ end
+
+ def destroy_backend
+ within_backend_terraform_working_directory do
+ RubyTerraform.destroy(
+ vars: backend_terraform_variables,
+ force: true
+ )
+ end
+ end
+
+ def stop_backend
+ within_backend_terraform_working_directory do
+ RubyTerraform.destroy(
+ vars: backend_terraform_variables,
+ target: 'aws_spot_instance_request.main',
+ force: true
+ )
+ end
+ end
+
+ def in_use_store_paths
+ [
+ GovukGuix::Revision.where(archived: false).pluck(:store_path),
+ available_data_snapshots.pluck(:store_path),
+ mini_environments
+ .where(archived: false)
+ .pluck(:backend_data)
+ .map { |x| x&.dig('build_output') }
+ ].flatten.compact
+ end
+
+ def update_guix_gcroots
+ GovukGuix::UpdateGcrootsDirectory.set_in_use_store_paths(
+ in_use_store_paths,
+ run_remotely_on_host: build_remote_host
+ )
+ end
+
+ def add_in_use_store_path(store_path)
+ GovukGuix::UpdateGcrootsDirectory.add_store_path(
+ store_path,
+ run_remotely_on_host: build_remote_host
+ )
+ end
+
+ def within_backend_terraform_working_directory(&block)
+ with_advisory_lock(
+ "terraform"
+ ) do
+ TerraformWorkingDirectory.new(
+ terraform_state_id,
+ 'terraform/aws_using_ami/backend'
+ ).within_working_directory(&block)
+ end
+ end
+
+ def available_data_snapshots
+ GovukGuix::DataSnapshot.where(backend: self)
+ end
+
+ def backend_terraform_states
+ TerraformState.where(
+ state_id: terraform_state_id
+ )
+ end
+
+ def backend_latest_terraform_state
+ backend_terraform_states.order(:id).last
+ end
+
+ def status
+ latest_terraform_state = backend_terraform_states.order(:id).last
+
+ running = (
+ latest_terraform_state &&
+ (latest_terraform_state.output_value('backend_up') == 'true')
+ )
+
+ {
+ running: running,
+ updated_at: latest_terraform_state.try(:created_at)
+ }
+ end
+end
diff --git a/app/models/backends/terraform_aws_using_ami/mini_environment_methods.rb b/app/models/backends/terraform_aws_using_ami/mini_environment_methods.rb
new file mode 100644
index 0000000..839bc3b
--- /dev/null
+++ b/app/models/backends/terraform_aws_using_ami/mini_environment_methods.rb
@@ -0,0 +1,117 @@
+# GOV.UK Mini Environment Admin
+# Copyright © 2018, 2019 Christopher Baines <mail@cbaines.net>
+#
+# This file is part of the GOV.UK Mini Environment Admin.
+#
+# The GOV.UK Mini Environment Admin is free software: you can
+# redistribute it and/or modify it under the terms of the GNU Affero
+# General Public License as published by the Free Software Foundation,
+# either version 3 of the License, or (at your option) any later
+# version.
+#
+# The GOV.UK Mini Environment Admin is distributed in the hope that it
+# will be useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public
+# License along with the GOV.UK Mini Environment Admin. If not, see
+# <http://www.gnu.org/licenses/>.
+
+module Backends::TerraformAwsUsingAmi::MiniEnvironmentMethods
+ def build(mini_environment)
+ slug = mini_environment.name.parameterize
+
+ GovukGuix::BuildMiniEnvironment.build(
+ mini_environment.id,
+ services: mini_environment.services.map(&:build_argument_string),
+ arguments: {
+ type: 'aws-packer-ami',
+ base_os: 'aws',
+ ami_name: "govuk-mini-environment-admin-#{slug}",
+ app_domain: "#{slug}.#{domain}",
+ web_domain: "www.#{slug}.#{domain}",
+ use_https: 'certbot',
+ signon_instance_name: slug,
+ admin_environment_label: mini_environment.name,
+ use_error_pages: 'true',
+ origin_basic_auth: "#{slug}=#{slug}",
+ },
+ run_remotely_on_host: mini_environment.backend.build_remote_host
+ )
+ end
+
+ def start(mini_environment)
+ logger.info "Setting up #{mini_environment.name}"
+
+ within_terraform_working_directory(mini_environment) do
+ RubyTerraform.apply(
+ vars: terraform_variables(mini_environment),
+ auto_approve: true
+ )
+ end
+ end
+
+ def destroy(mini_environment)
+ within_terraform_working_directory(mini_environment) do
+ RubyTerraform.destroy(
+ vars: terraform_variables(mini_environment),
+ force: true
+ )
+ end
+ end
+
+ def refresh(mini_environment)
+ within_terraform_working_directory(mini_environment) do
+ RubyTerraform.refresh(
+ vars: terraform_variables(mini_environment)
+ )
+ end
+ end
+
+ def terraform_states(mini_environment)
+ TerraformState.where(
+ state_id: mini_environment_state_id(mini_environment)
+ )
+ end
+
+ def within_terraform_working_directory(mini_environment, &block)
+ with_advisory_lock(
+ "terraform"
+ ) do
+ TerraformWorkingDirectory.new(
+ mini_environment_state_id(mini_environment),
+ 'terraform/aws_using_ami/mini_environment'
+ ).within_working_directory(&block)
+ end
+ end
+
+ def mini_environment_state_id(mini_environment)
+ "mini_environment/#{mini_environment.id}"
+ end
+
+ def signon_url(mini_environment)
+ "https://signon.#{mini_environment.name.parameterize}.#{domain}"
+ end
+
+ def terraform_variables(mini_environment)
+ credentials = TerraformHttpBackendController.credentials
+
+ ami_id = mini_environment.backend_data['build_output'].split(' ')[1]
+
+ common_terraform_variables.merge(
+ slug: mini_environment.name.parameterize,
+ ami_id: ami_id,
+ backend_remote_state_address: (
+ Plek.new.external_url_for('mini-environment-admin') +
+ Rails
+ .application
+ .routes
+ .url_helpers
+ .terraform_http_backend_path(terraform_state_id)
+ ),
+ backend_remote_state_username: credentials[:name],
+ backend_remote_state_password: credentials[:password]
+ )
+ end
+end
diff --git a/app/services/govuk_guix/build_mini_environment.rb b/app/services/govuk_guix/build_mini_environment.rb
index 9cada20..a87c4da 100644
--- a/app/services/govuk_guix/build_mini_environment.rb
+++ b/app/services/govuk_guix/build_mini_environment.rb
@@ -72,12 +72,16 @@ module GovukGuix::BuildMiniEnvironment
run_remotely_on_host: remote_host
)
- build_output = output.last.strip
-
- raise 'InvalidOutput' unless build_output.starts_with? '/gnu/store'
-
+ last_non_empty_line = output.reverse.find do |line|
+ !line.strip.empty?
+ end
+ build_output = last_non_empty_line.strip
logger.debug(self.class) { "build_output: #{build_output}" }
+ unless options[:arguments][:type] == 'aws-packer-ami'
+ raise 'InvalidOutput' unless build_output.starts_with? '/gnu/store'
+ end
+
mini_environment.update(
backend_data: {
build_output: build_output
diff --git a/app/views/backends/terraform_aws_using_ami/new.html.erb b/app/views/backends/terraform_aws_using_ami/new.html.erb
new file mode 100644
index 0000000..929d69f
--- /dev/null
+++ b/app/views/backends/terraform_aws_using_ami/new.html.erb
@@ -0,0 +1,192 @@
+<%#
+
+GOV.UK Mini Environment Admin
+Copyright © 2018, 2019 Christopher Baines <mail@cbaines.net>
+
+This file is part of the GOV.UK Mini Environment Admin.
+
+The GOV.UK Mini Environment Admin is free software: you can
+redistribute it and/or modify it under the terms of the GNU Affero
+General Public License as published by the Free Software Foundation,
+either version 3 of the License, or (at your option) any later
+version.
+
+The GOV.UK Mini Environment Admin is distributed in the hope that it
+will be useful, but WITHOUT ANY WARRANTY; without even the implied
+warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+the GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public
+License along with the GOV.UK Mini Environment Admin. If not, see
+<http://www.gnu.org/licenses/>.
+
+%>
+
+<a href="<%= setup_path %>" class="btn btn-lg btn-default pull-right">
+ Back to setup
+</a>
+
+<h1>Create a new AWS backend</h1>
+
+<div class="row">
+ <div class="col-md-10">
+ <%= form_with(model: @backend,
+ url: { action: "create" },
+ html: { class: "form-horizontal" }) do |f|
+ %>
+
+ <div class="form-group form-group-lg">
+ <%= f.label :label, class: 'col-sm-4 control-label' %>
+ <div class="col-sm-8">
+ <%= f.text_field(
+ :label,
+ class: 'form-control',
+ placeholder: 'Label for this backend'
+ ) %>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label :domain, class: 'col-sm-4 control-label' %>
+ <div class="col-sm-8">
+ <%= f.text_field(
+ :domain,
+ class: 'form-control',
+ placeholder: 'Domain within which to host mini environments'
+ ) %>
+ <span class="help-block">
+ <p>
+ For example, if you entered <samp>example.com</samp> for
+ the backend domain, and then created a mini environment
+ called "Test" using this backend, then the application
+ domain for the mini environment would be
+ <samp>test.example.com</samp>.
+ </p>
+ <p>
+ A Route53 Hosted Zone will be created for this domain,
+ and records added for the mini environments.
+ </p>
+ </span>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label :aws_region, 'AWS Region', class: 'col-sm-4 control-label' %>
+ <div class="col-sm-8">
+ <%= f.text_field(
+ :aws_region,
+ class: 'form-control',
+ placeholder: 'What region to use'
+ ) %>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label(
+ :vpc_id,
+ 'VPC ID',
+ class: 'col-sm-4 control-label'
+ ) %>
+ <div class="col-sm-8">
+ <%= f.text_field(
+ :vpc_id,
+ class: 'form-control',
+ placeholder: 'The ID of the VPC (Virtual Private Cloud) to use'
+ ) %>
+ <span class="help-block">
+ <p>
+ This VPC (Virtual Private Cloud) should be the one to
+ use for all resources.
+ </p>
+ </span>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label(
+ :route_53_zone_id,
+ 'Route 53 Zone ID',
+ class: 'col-sm-4 control-label'
+ ) %>
+ <div class="col-sm-8">
+ <%= f.text_field(
+ :route_53_zone_id,
+ class: 'form-control',
+ placeholder: 'The ID of the Route 53 Zone to use'
+ ) %>
+ <span class="help-block">
+ <p>
+ This zone should be authoritive for the domain this
+ backend is using. Entries in this zone will be created
+ for the mini environments.
+ </p>
+ </span>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label :aws_access_key_id, 'AWS Access Key ID', class: 'col-sm-4 control-label' %>
+ <div class="col-sm-8">
+ <%= f.text_field(
+ :aws_access_key_id,
+ class: 'form-control',
+ ) %>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label :aws_secret_access_key, 'AWS Secret Access Key', class: 'col-sm-4 control-label' %>
+ <div class="col-sm-8">
+ <%= f.password_field(
+ :aws_secret_access_key,
+ class: 'form-control',
+ ) %>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label(
+ :ssh_public_key,
+ 'SSH Key, public part',
+ class: 'col-sm-4 control-label'
+ ) %>
+ <div class="col-sm-8">
+ <%= f.text_area(
+ :ssh_public_key,
+ class: 'form-control',
+ placeholder: 'The public part of the SSH key to use'
+ ) %>
+ <span class="help-block">
+ <p>
+ </p>
+ </span>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label(
+ :ssh_private_key,
+ 'SSH Key, private part',
+ class: 'col-sm-4 control-label'
+ ) %>
+ <div class="col-sm-8">
+ <%= f.text_area(
+ :ssh_private_key,
+ class: 'form-control',
+ placeholder: 'The private part of the SSH key to use'
+ ) %>
+ <span class="help-block">
+ <p>
+ </p>
+ </span>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <div class="col-sm-offset-4 col-sm-8">
+ <%= f.submit "Create", class: 'btn btn-lg btn-success' %>
+ </div>
+ </div>
+ <% end %>
+ </div>
+</div>
diff --git a/app/views/backends/terraform_aws_using_ami/show.html.erb b/app/views/backends/terraform_aws_using_ami/show.html.erb
new file mode 100644
index 0000000..14810d1
--- /dev/null
+++ b/app/views/backends/terraform_aws_using_ami/show.html.erb
@@ -0,0 +1,362 @@
+<%#
+
+GOV.UK Mini Environment Admin
+Copyright © 2018, 2019 Christopher Baines <mail@cbaines.net>
+
+This file is part of the GOV.UK Mini Environment Admin.
+
+The GOV.UK Mini Environment Admin is free software: you can
+redistribute it and/or modify it under the terms of the GNU Affero
+General Public License as published by the Free Software Foundation,
+either version 3 of the License, or (at your option) any later
+version.
+
+The GOV.UK Mini Environment Admin is distributed in the hope that it
+will be useful, but WITHOUT ANY WARRANTY; without even the implied
+warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+the GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public
+License along with the GOV.UK Mini Environment Admin. If not, see
+<http://www.gnu.org/licenses/>.
+
+%>
+
+<a href="<%= setup_path %>" class="btn btn-lg btn-default pull-right">
+ Back to Setup
+</a>
+
+<h1>Backend: <%= @backend.label %></h1>
+<% status = @backend.status %>
+
+<br>
+
+<div class="row">
+ <div class="col-md-3">
+
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ Current Status<br>
+ <small>updated at <%= status[:updated_at] %></small>
+ </div>
+ <div class="panel-body">
+ <% if status[:running] %>
+ <div class="alert alert-success text-center"
+ role="alert"
+ style="margin-bottom: 0px;">
+ Backend available
+ </div>
+ <% else %>
+ <div class="alert alert-warning text-center"
+ role="alert"
+ style="margin-bottom: 0px;">
+ Backend down
+ </div>
+ <% end %>
+
+ <br>
+ <a href="<%= terraform_http_backend_show_history_path(
+ @backend.terraform_state_id
+ ) %>">
+ View Terraform state information
+ </a>
+ </div>
+ </div>
+
+ <%= form_with(
+ url: perform_action_terraform_aws_using_ami_backend_path(@backend),
+ local: true,
+ method: "post"
+ ) do %>
+
+ <div class="panel panel-default">
+ <div class="panel-heading">Actions</div>
+
+ <ul class="list-group">
+ <li class="list-group-item">
+ <%= submit_tag('Deploy',
+ role: 'button',
+ style: 'margin-bottom: 5px;',
+ class: 'btn btn-lg btn-success btn-block')
+ %>
+ <p class="list-group-item-text">
+ Run Terraform to deploy this backend, ensuring everything
+ is setup to create new mini environments.
+ </p>
+ </li>
+ <li class="list-group-item">
+ <%= submit_tag("Stop",
+ role: 'button',
+ style: 'margin-bottom: 5px;',
+ class: 'btn btn-lg btn-warning btn-block')
+ %>
+ <p class="list-group-item-text">
+ Run Terraform to stop this backend, terminating the
+ build machine used to build new mini
+ environments. Existing environments will continue to
+ run, but new environments cannot be created.
+ </p>
+ </li>
+ <li class="list-group-item">
+ <%= submit_tag("Destroy",
+ role: 'button',
+ style: 'margin-bottom: 5px;',
+ class: 'btn btn-lg btn-danger btn-block')
+ %>
+ <p class="list-group-item-text">
+ Run Terraform to destroy this backend, note that this will
+ remove all cached data and disrupt all mini environments
+ using this backend.
+ </p>
+ </li>
+ <li class="list-group-item">
+ <%= submit_tag("Refresh state",
+ role: 'button',
+ style: 'margin-bottom: 5px;',
+ class: 'btn btn-lg btn-info btn-block')
+ %>
+ <p class="list-group-item-text">
+ Run Terraform to refresh the state information for this
+ backend.
+ </p>
+ </li>
+ </ul>
+ </div>
+ <% end %>
+ </div>
+ <div class="col-md-9">
+ <%= render(
+ partial: 'shared/jobs',
+ locals: {
+ jobs: Backends::TerraformAwsJob.jobs(@backend.id).order(id: :desc)
+ }
+ ) %>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-8">
+ <h3>Update details</h3>
+ <br>
+
+ <%= form_with(model: @backend,
+ url: { action: "update" },
+ html: { class: "form-horizontal" }) do |f|
+ %>
+
+ <div class="form-group form-group-lg">
+ <%= f.label :label, class: 'col-sm-4 control-label' %>
+ <div class="col-sm-8">
+ <%= f.text_field(
+ :label,
+ class: 'form-control',
+ placeholder: 'Label for this backend'
+ ) %>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label :domain, class: 'col-sm-4 control-label' %>
+ <div class="col-sm-8">
+ <%= f.text_field(
+ :domain,
+ class: 'form-control',
+ placeholder: 'Domain within which to host mini environments',
+ readonly: true
+ ) %>
+ <span class="help-block">
+ <p>
+ For example, if you entered <samp>example.com</samp> for
+ the backend domain, and then created a mini environment
+ called "Test" using this backend, then the application
+ domain for the mini environment would be
+ <samp>test.example.com</samp>.
+ </p>
+ <p>
+ A Route53 Hosted Zone will be created for this domain,
+ and records added for the mini environments.
+ </p>
+ </span>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label :aws_region, 'AWS Region', class: 'col-sm-4 control-label' %>
+ <div class="col-sm-8">
+ <%= f.text_field(
+ :aws_region,
+ class: 'form-control',
+ placeholder: 'What region to use',
+ readonly: true,
+ ) %>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label(
+ :vpc_id,
+ 'VPC ID',
+ class: 'col-sm-4 control-label'
+ ) %>
+ <div class="col-sm-8">
+ <%= f.text_field(
+ :vpc_id,
+ class: 'form-control',
+ placeholder: 'The ID of the VPC (Virtual Private Cloud) to use',
+ readonly: true
+ ) %>
+ <span class="help-block">
+ <p>
+ This VPC (Virtual Private Cloud) should be the one to
+ use for all resources.
+ </p>
+ </span>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label(
+ :route_53_zone_id,
+ 'Route 53 Zone ID',
+ class: 'col-sm-4 control-label'
+ ) %>
+ <div class="col-sm-8">
+ <%= f.text_field(
+ :route_53_zone_id,
+ class: 'form-control',
+ placeholder: 'The ID of the Route 53 Zone to use',
+ readonly: true
+ ) %>
+ <span class="help-block">
+ <p>
+ This zone should be authoritive for the domain this
+ backend is using. Entries in this zone will be created
+ for the mini environments.
+ </p>
+ </span>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label :aws_access_key_id, 'AWS Access Key ID', class: 'col-sm-4 control-label' %>
+ <div class="col-sm-8">
+ <%= f.text_field(
+ :aws_access_key_id,
+ class: 'form-control',
+ ) %>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label :aws_secret_access_key, 'AWS Secret Access Key', class: 'col-sm-4 control-label' %>
+ <div class="col-sm-8">
+ <%= f.password_field(
+ :aws_secret_access_key,
+ class: 'form-control',
+ placeholder: 'Secret key hidden',
+ ) %>
+ <span id="helpBlock" class="help-block">
+ The AWS Secret Access Key is not accessible once entered.
+ </span>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label(
+ :ssh_public_key,
+ 'SSH Key, public part',
+ class: 'col-sm-4 control-label'
+ ) %>
+ <div class="col-sm-8">
+ <%= f.text_area(
+ :ssh_public_key,
+ class: 'form-control',
+ placeholder: 'The public part of the SSH key to use',
+ readonly: true
+ ) %>
+ <span class="help-block">
+ <p>
+ </p>
+ </span>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <%= f.label(
+ :ssh_private_key,
+ 'SSH Key, private part',
+ class: 'col-sm-4 control-label'
+ ) %>
+ <div class="col-sm-8">
+ <%= text_area_tag(
+ :ssh_private_key,
+ 'Secret key hidden',
+ class: 'form-control',
+ disabled: true
+ ) %>
+ <span class="help-block">
+ <p>
+ </p>
+ </span>
+ </div>
+ </div>
+
+ <div class="form-group form-group-lg">
+ <div class="col-sm-offset-2 col-sm-10">
+ <%= f.submit "Save", class: 'btn btn-lg btn-success' %>
+ </div>
+ </div>
+ <% end %>
+ </div>
+ <div class="col-md-4">
+ <h3>Delete backend</h3>
+ <br>
+
+ <% unless @backend.mini_environments.empty? %>
+ <p>
+ Unable to delete backend, as mini environments using this
+ backend still exist.
+ </p>
+
+ <p>
+ To delete this backend, first delete all the mini environments
+ using it.
+ </p>
+ <% end %>
+
+ <%= form_with(model: @backend,
+ url: { action: "destroy" },
+ html: { class: "form-horizontal", method: :delete }) do |f|
+ %>
+ <%= f.submit(
+ "Delete",
+ class: (
+ 'btn btn-lg btn-danger' +
+ (@backend.mini_environments.empty? ? '' : ' disabled')
+ )
+ ) %>
+ <% end %>
+
+ </div>
+</div>
+
+<h3>Mini environments</h3>
+
+<table class="table table-striped">
+ <tr>
+ <th>Name</th>
+ <th></th>
+ </tr>
+ <% @backend.mini_environments.each do |mini_environment| %>
+ <tr>
+ <td><%= mini_environment.name %></td>
+ <td>
+ <a class="btn btn-default btn-lg pull-right"
+ role="button"
+ href="<%= mini_environment_path(mini_environment) %>">
+ Show details
+ </a>
+ </td>
+ </tr>
+ <% end %>
+</table>
diff --git a/config/routes.rb b/config/routes.rb
index 8242395..507c77a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -23,6 +23,15 @@ Rails.application.routes.draw do
end
end
+ resources :terraform_aws_using_ami,
+ as: 'terraform_aws_using_ami_backends',
+ controller: 'backends/terraform_aws_using_ami',
+ only: %i[create new show update destroy] do
+ member do
+ post 'perform_action'
+ end
+ end
+
resources :terraform_libvirt,
as: 'terraform_libvirt_backends',
controller: 'backends/terraform_libvirt',
diff --git a/db/migrate/20190519091614_terraform_aws_using_ami.rb b/db/migrate/20190519091614_terraform_aws_using_ami.rb
new file mode 100644
index 0000000..0375a2a
--- /dev/null
+++ b/db/migrate/20190519091614_terraform_aws_using_ami.rb
@@ -0,0 +1,17 @@
+class TerraformAwsUsingAmi < ActiveRecord::Migration[5.2]
+ def change
+ create_table :terraform_aws_using_ami_backends do |t|
+ t.string :label
+ t.string :aws_region
+ t.string :aws_access_key_id
+ t.string :aws_secret_access_key
+ t.string :domain
+ t.string :route_53_zone_id, null: false
+ t.string :vpc_id, null: false
+ t.string :ssh_public_key
+ t.string :ssh_private_key
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/structure.sql b/db/structure.sql
index 5e5aaba..7fd8ca2 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -459,6 +459,45 @@ ALTER SEQUENCE public.terraform_aws_backends_id_seq OWNED BY public.terraform_aw
--
+-- Name: terraform_aws_using_ami_backends; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.terraform_aws_using_ami_backends (
+ id bigint NOT NULL,
+ label character varying,
+ aws_region character varying,
+ aws_access_key_id character varying,
+ aws_secret_access_key character varying,
+ domain character varying,
+ route_53_zone_id character varying NOT NULL,
+ vpc_id character varying NOT NULL,
+ ssh_public_key character varying,
+ ssh_private_key character varying,
+ created_at timestamp without time zone NOT NULL,
+ updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: terraform_aws_using_ami_backends_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.terraform_aws_using_ami_backends_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: terraform_aws_using_ami_backends_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.terraform_aws_using_ami_backends_id_seq OWNED BY public.terraform_aws_using_ami_backends.id;
+
+
+--
-- Name: terraform_libvirt_backends; Type: TABLE; Schema: public; Owner: -
--
@@ -595,6 +634,13 @@ ALTER TABLE ONLY public.terraform_aws_backends ALTER COLUMN id SET DEFAULT nextv
--
+-- Name: terraform_aws_using_ami_backends id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.terraform_aws_using_ami_backends ALTER COLUMN id SET DEFAULT nextval('public.terraform_aws_using_ami_backends_id_seq'::regclass);
+
+
+--
-- Name: terraform_libvirt_backends id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -696,6 +742,14 @@ ALTER TABLE ONLY public.terraform_aws_backends
--
+-- Name: terraform_aws_using_ami_backends terraform_aws_using_ami_backends_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.terraform_aws_using_ami_backends
+ ADD CONSTRAINT terraform_aws_using_ami_backends_pkey PRIMARY KEY (id);
+
+
+--
-- Name: terraform_libvirt_backends terraform_libvirt_backends_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -879,6 +933,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20180621065525'),
('20180621220505'),
('20180623083735'),
-('20190106112141');
+('20190106112141'),
+('20190519091614');
diff --git a/terraform/aws_using_ami/backend/guix-daemon.service.tpl b/terraform/aws_using_ami/backend/guix-daemon.service.tpl
new file mode 100644
index 0000000..184e3d5
--- /dev/null
+++ b/terraform/aws_using_ami/backend/guix-daemon.service.tpl
@@ -0,0 +1,17 @@
+[Unit]
+Description=Build daemon for GNU Guix
+
+[Service]
+ExecStart=/var/guix/profiles/per-user/root/current-guix/bin/guix-daemon --build-users-group=guixbuild --substitute-urls="${substitute_servers}" --listen=0.0.0.0 --listen=/var/guix/daemon-socket/socket --max-jobs=4
+Environment=GUIX_LOCPATH='/var/guix/profiles/per-user/root/guix-profile/lib/locale' LC_ALL=en_US.utf8 TMPDIR=/gnu/tmp
+RemainAfterExit=yes
+StandardOutput=syslog
+StandardError=syslog
+
+# See <https://lists.gnu.org/archive/html/guix-devel/2016-04/msg00608.html>.
+# Some package builds (for example, go@1.8.1) may require even more than
+# 1024 tasks.
+TasksMax=8192
+
+[Install]
+WantedBy=multi-user.target
diff --git a/terraform/aws_using_ami/backend/main.tf b/terraform/aws_using_ami/backend/main.tf
new file mode 100644
index 0000000..fead443
--- /dev/null
+++ b/terraform/aws_using_ami/backend/main.tf
@@ -0,0 +1,437 @@
+terraform {
+ backend "http" {}
+}
+
+variable "aws_access_key" {
+ type = "string"
+}
+
+variable "aws_secret_key" {
+ type = "string"
+}
+
+variable "aws_region" {
+ type = "string"
+}
+
+variable "aws_vpc_id" {
+ type = "string"
+}
+
+variable "aws_route_53_zone_id" {
+ type = "string"
+}
+
+variable "ssh_public_key" {
+ type = "string"
+}
+
+variable "ssh_private_key" {
+ type = "string"
+}
+
+variable "guix_substitute_servers" {
+ type = "map"
+ default = {
+ "https://ci.guix.gnu.org" = <<EOF
+(entry
+ (public-key
+ (ecc
+ (curve Ed25519)
+ (q #8D156F295D24B0D9A86FA5741A840FF2D24F60F7B6C4134814AD55625971B394#)
+ )
+ )
+ (tag
+ (guix import)
+ )
+)
+EOF
+ }
+}
+
+variable "mini_environment_admin_guix_public_key" {
+ type = "string"
+}
+
+variable "mini_environment_admin_egress_cidr_blocks" {
+ type = "list"
+}
+
+variable "backend_slug" {
+ type = "string"
+}
+
+locals {
+ guix_daemon_substitute_servers = "${join(" ", keys(var.guix_substitute_servers))}"
+}
+
+provider "aws" {
+ access_key = "${var.aws_access_key}"
+ secret_key = "${var.aws_secret_key}"
+ region = "${var.aws_region}"
+}
+
+data "aws_route53_zone" "main" {
+ zone_id = "${var.aws_route_53_zone_id}"
+}
+
+data "template_file" "guix_daemon_service" {
+ template = "${file("${path.module}/guix-daemon.service.tpl")}"
+
+ vars {
+ substitute_servers = "${local.guix_daemon_substitute_servers}"
+ }
+}
+
+data "aws_availability_zones" "available" {}
+
+resource "aws_key_pair" "deployer" {
+ key_name = "govuk_mini_environment_admin/${var.backend_slug}/deployer"
+ public_key = "${var.ssh_public_key}"
+}
+
+resource "aws_security_group" "public_webserver" {
+ name = "govuk_mini_environment_admin/${var.backend_slug}/public_webserver"
+ description = "For instances running public facing web servers"
+ vpc_id = "${var.aws_vpc_id}"
+
+ ingress {
+ from_port = 0
+ to_port = 80
+ protocol = "tcp"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+
+ ingress {
+ from_port = 0
+ to_port = 443
+ protocol = "tcp"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+
+ ingress {
+ from_port = 0
+ to_port = 8080
+ protocol = "tcp"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+
+ ingress {
+ from_port = 0
+ to_port = 8443
+ protocol = "tcp"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+}
+
+resource "aws_security_group" "ssh_access_from_mini_environment_admin" {
+ name = "govuk_mini_environment_admin/${var.backend_slug}/ssh_access_from_mini_environment_admin"
+ description = "For instances that need SSH access for Terraform and Guix builds"
+ vpc_id = "${var.aws_vpc_id}"
+
+ ingress {
+ from_port = 0
+ to_port = 22
+ protocol = "tcp"
+ cidr_blocks = [
+ "${var.mini_environment_admin_egress_cidr_blocks}"
+ ]
+ }
+}
+
+resource "aws_security_group" "guix_client" {
+ name = "govuk_mini_environment_admin/${var.backend_slug}/guix_client"
+ description = "For instances with access to the guix_daemon instance"
+ vpc_id = "${var.aws_vpc_id}"
+
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+}
+
+resource "aws_security_group" "guix_publish" {
+ name = "govuk_mini_environment_admin/${var.backend_slug}/guix_publish"
+ description = "For guix publish on the builder instance."
+ vpc_id = "${var.aws_vpc_id}"
+
+ ingress {
+ from_port = 80
+ to_port = 80
+ protocol = "tcp"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+}
+
+resource "aws_security_group" "guix_daemon" {
+ name = "govuk_mini_environment_admin/${var.backend_slug}/guix_daemon"
+ description = "For the guix_daemon instance."
+ vpc_id = "${var.aws_vpc_id}"
+
+ ingress {
+ from_port = 44146
+ to_port = 44146
+ protocol = "tcp"
+ security_groups = ["${aws_security_group.guix_client.id}"]
+ }
+
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+}
+
+resource "aws_iam_policy" "packer" {
+ name = "packer_policy"
+ path = "/govuk/mini_environment_admin/${var.backend_slug}/"
+ description = "Policy to enable using packer to build AMIs"
+
+ policy = <<EOF
+{
+ "Version": "2012-10-17",
+ "Statement": [{
+ "Effect": "Allow",
+ "Action" : [
+ "ec2:AttachVolume",
+ "ec2:AuthorizeSecurityGroupIngress",
+ "ec2:CopyImage",
+ "ec2:CreateImage",
+ "ec2:CreateKeypair",
+ "ec2:CreateSecurityGroup",
+ "ec2:CreateSnapshot",
+ "ec2:CreateTags",
+ "ec2:CreateVolume",
+ "ec2:DeleteKeyPair",
+ "ec2:DeleteSecurityGroup",
+ "ec2:DeleteSnapshot",
+ "ec2:DeleteVolume",
+ "ec2:DeregisterImage",
+ "ec2:DescribeImageAttribute",
+ "ec2:DescribeImages",
+ "ec2:DescribeInstances",
+ "ec2:DescribeInstanceStatus",
+ "ec2:DescribeRegions",
+ "ec2:DescribeSecurityGroups",
+ "ec2:DescribeSnapshots",
+ "ec2:DescribeSubnets",
+ "ec2:DescribeTags",
+ "ec2:DescribeVolumes",
+ "ec2:DetachVolume",
+ "ec2:GetPasswordData",
+ "ec2:ModifyImageAttribute",
+ "ec2:ModifyInstanceAttribute",
+ "ec2:ModifySnapshotAttribute",
+ "ec2:RegisterImage",
+ "ec2:RunInstances",
+ "ec2:StopInstances",
+ "ec2:TerminateInstances"
+ ],
+ "Resource" : "*"
+ }]
+}
+EOF
+}
+
+resource "aws_iam_role" "builder" {
+ name = "builder_role"
+
+ assume_role_policy = <<EOF
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Principal": {
+ "Service": "ec2.amazonaws.com"
+ },
+ "Effect": "Allow",
+ "Sid": ""
+ }
+ ]
+}
+EOF
+}
+
+resource "aws_iam_role_policy_attachment" "builder_role_packer_policy" {
+ role = "${aws_iam_role.builder.name}"
+ policy_arn = "${aws_iam_policy.packer.arn}"
+}
+
+resource "aws_iam_instance_profile" "builder" {
+ name = "builder_profile"
+ role = "${aws_iam_role.builder.name}"
+}
+
+resource "aws_spot_instance_request" "main" {
+ ami = "ami-0b2a4d260c54e8d3d"
+ instance_type = "i3.xlarge"
+ key_name = "${aws_key_pair.deployer.key_name}"
+ security_groups = [
+ "${aws_security_group.guix_daemon.name}",
+ "${aws_security_group.guix_publish.name}",
+ "${aws_security_group.ssh_access_from_mini_environment_admin.name}"
+ ]
+ iam_instance_profile = "${aws_iam_instance_profile.builder.name}"
+
+ wait_for_fulfillment = true
+ spot_price = "0.172"
+ spot_type = "one-time"
+ availability_zone = "${data.aws_availability_zones.available.names[1]}"
+
+ root_block_device {
+ volume_size = "50"
+ }
+
+ provisioner "file" {
+ content = "${data.template_file.guix_daemon_service.rendered}"
+ destination = "/home/ubuntu/guix-daemon.service"
+
+ connection {
+ type = "ssh"
+ user = "ubuntu"
+ private_key = "${var.ssh_private_key}"
+ }
+ }
+
+ provisioner "file" {
+ content = "(acl\n ${join("\n", values(var.guix_substitute_servers))}\n${var.mini_environment_admin_guix_public_key}\n)\n"
+ destination = "/home/ubuntu/acl"
+
+ connection {
+ type = "ssh"
+ user = "ubuntu"
+ private_key = "${var.ssh_private_key}"
+ }
+ }
+
+ provisioner "remote-exec" {
+ inline = [
+ <<EOF
+sudo sgdisk --largest-new=0 /dev/nvme0n1
+
+sudo mkfs.ext4 -m 0 /dev/nvme0n1p1
+
+echo "/dev/nvme0n1p1 /gnu ext4 defaults,discard 0 0" | sudo tee -a /etc/fstab
+
+sudo cat /etc/fstab
+
+sudo mkdir -p /gnu
+sudo mount -a
+sudo mkdir -p /gnu/tmp
+EOF
+ ,
+ <<EOF
+set -e
+
+FILENAME="guix-binary-1.0.0.x86_64-linux.tar.xz"
+
+wget https://ftp.gnu.org/gnu/guix/$FILENAME
+
+cd /
+sudo tar --warning=no-timestamp -xf /home/ubuntu/$FILENAME
+cd -
+
+rm "$FILENAME"
+
+sudo mkdir -p ~root/.config/guix
+sudo ln -sf /var/guix/profiles/per-user/root/current-guix ~root/.config/guix/current
+
+sudo mkdir -p /usr/local/bin
+sudo ln --symbolic --no-target-directory /var/guix/profiles/per-user/root/current-guix/bin/guix /usr/local/bin/guix
+EOF
+ ,
+ "sudo mv /home/ubuntu/guix-daemon.service /etc/systemd/system/guix-daemon.service",
+ "sudo mkdir /etc/guix",
+ "sudo mv /home/ubuntu/acl /etc/guix/acl",
+ "sudo groupadd --system guixbuild",
+ <<EOF
+for i in `seq -w 1 32`;
+do
+ sudo useradd -g guixbuild -G guixbuild \
+ -d /var/empty -s `which nologin` \
+ -c "Guix build user $i" --system \
+ guix$i;
+done
+EOF
+ ,
+ # Disable apparmor, as it seems to cause guile to segfault
+ # sometimes
+ "sudo systemctl stop apparmor",
+ "sudo service apparmor teardown",
+ "sudo systemctl disable apparmor",
+ "sudo systemctl daemon-reload",
+ "sudo systemctl enable guix-daemon.service",
+ "sudo systemctl start guix-daemon.service",
+ "sudo mkdir -p /var/guix/gcroots/govuk-development-data /var/guix/gcroots/govuk-mini-environment-admin",
+ "sudo chown ubuntu:ubuntu /var/guix/gcroots/govuk-development-data /var/guix/gcroots/govuk-mini-environment-admin",
+ "mkdir -p ~/.config", # Make the ~/.config directory, to ensure it's owned by ubuntu
+ # Setup the profile for the ubuntu user
+ <<EOF
+if [ ! -d "/var/guix/profiles/per-user/ubuntu" ]; then
+ # Install openssl and nss-certs so that downloads work for custom
+ # package versions, and guile-sqlite3
+ /var/guix/profiles/per-user/root/current-guix/bin/guix package --fallback -i glibc-locales guile bash openssl nss-certs guile-sqlite3
+fi
+EOF
+ ,
+ # This is needed for things like guix copy to work
+ <<EOF
+echo 'export GUIX_LOCPATH="$HOME/.guix-profile/lib/locale"
+
+GUIX_PROFILE=/home/ubuntu/.guix-profile; source /home/ubuntu/.guix-profile/etc/profile
+GUIX_PROFILE=/home/ubuntu/.config/guix/current; source /home/ubuntu/.config/guix/current/etc/profile
+' | cat - .bashrc > temp && mv temp .bashrc
+EOF
+ ]
+
+ connection {
+ type = "ssh"
+ user = "ubuntu"
+ private_key = "${var.ssh_private_key}"
+ }
+ }
+}
+
+resource "aws_route53_record" "main" {
+ zone_id = "${data.aws_route53_zone.main.zone_id}"
+ name = "builder.${var.backend_slug}"
+ type = "A"
+ ttl = "60"
+ records = ["${aws_spot_instance_request.main.public_ip}"]
+
+ depends_on = ["aws_spot_instance_request.main"]
+}
+
+# Outputs
+
+output "backend_up" {
+ value = "${length(aws_spot_instance_request.main.public_ip) != 0}"
+}
+
+output "deployer_key_pair_name" {
+ value = "${aws_key_pair.deployer.key_name}"
+}
+
+output "guix_client_security_group_name" {
+ value = "${aws_security_group.guix_client.name}"
+}
+
+output "public_webserver_security_group_name" {
+ value = "${aws_security_group.public_webserver.name}"
+}
+
+output "ssh_access_from_mini_environment_admin_security_group_name" {
+ value = "${aws_security_group.ssh_access_from_mini_environment_admin.name}"
+}
+
+output "guix_daemon_private_dns" {
+ value = "${aws_spot_instance_request.main.private_dns}"
+}
+
+output "guix_daemon_public_dns" {
+ value = "${aws_spot_instance_request.main.public_dns}"
+}
diff --git a/terraform/aws_using_ami/mini_environment/main.tf b/terraform/aws_using_ami/mini_environment/main.tf
new file mode 100644
index 0000000..08445af
--- /dev/null
+++ b/terraform/aws_using_ami/mini_environment/main.tf
@@ -0,0 +1,114 @@
+terraform {
+ backend "http" {}
+}
+
+variable "slug" {
+ type = "string"
+}
+
+variable "aws_access_key" {
+ type = "string"
+}
+
+variable "aws_secret_key" {
+ type = "string"
+}
+
+variable "aws_region" {
+ type = "string"
+}
+
+variable "aws_route_53_zone_id" {
+ type = "string"
+}
+
+variable "ami_id" {
+ type = "string"
+}
+
+variable "backend_remote_state_address" {
+ type = "string"
+}
+
+variable "backend_remote_state_username" {
+ type = "string"
+}
+
+variable "backend_remote_state_password" {
+ type = "string"
+}
+
+variable "ssh_private_key" {
+ type = "string"
+}
+
+provider "aws" {
+ access_key = "${var.aws_access_key}"
+ secret_key = "${var.aws_secret_key}"
+ region = "${var.aws_region}"
+}
+
+
+data "terraform_remote_state" "backend" {
+ backend = "http"
+ config {
+ address = "${var.backend_remote_state_address}"
+ username = "${var.backend_remote_state_username}"
+ password = "${var.backend_remote_state_password}"
+ }
+}
+
+data "aws_route53_zone" "main" {
+ zone_id = "${var.aws_route_53_zone_id}"
+}
+
+
+resource "aws_spot_instance_request" "main" {
+ ami = "${var.ami_id}"
+ instance_type = "t2.xlarge"
+ key_name = "${data.terraform_remote_state.backend.deployer_key_pair_name}"
+ security_groups = [
+ "${data.terraform_remote_state.backend.guix_client_security_group_name}",
+ "${data.terraform_remote_state.backend.public_webserver_security_group_name}",
+ "${data.terraform_remote_state.backend.ssh_access_from_mini_environment_admin_security_group_name}"
+ ]
+
+ wait_for_fulfillment = true
+ spot_price = "0.21"
+ spot_type = "one-time"
+
+ root_block_device {
+ volume_size = "200"
+ }
+}
+
+resource "aws_route53_record" "main" {
+ zone_id = "${data.aws_route53_zone.main.zone_id}"
+ name = "${var.slug}"
+ type = "A"
+ ttl = "60"
+ records = ["${aws_spot_instance_request.main.public_ip}"]
+}
+
+resource "aws_route53_record" "wildcard" {
+ zone_id = "${data.aws_route53_zone.main.zone_id}"
+ name = "*.${var.slug}"
+ type = "A"
+ ttl = "60"
+ records = ["${aws_spot_instance_request.main.public_ip}"]
+}
+
+# Outputs
+
+output "spot_bid_status" {
+ value = "${aws_spot_instance_request.main.spot_bid_status}"
+}
+
+output "spot_request_status" {
+ value = "${aws_spot_instance_request.main.spot_request_state}"
+}
+
+output "mini_environment_up" {
+ value = "${aws_spot_instance_request.main.spot_bid_status == "fulfilled"}"
+}
+