diff options
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"}" +} + |