Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional improvements over #49 #51

Merged
merged 14 commits into from
Jan 27, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions build.tf
Original file line number Diff line number Diff line change
@@ -106,7 +106,7 @@ resource "aws_codebuild_project" "docker" {

environment {
compute_type = "BUILD_GENERAL1_MEDIUM"
image = "aws/codebuild/standard:6.0"
image = "aws/codebuild/standard:7.0"
type = "LINUX_CONTAINER"
image_pull_credentials_type = "CODEBUILD"
privileged_mode = true
@@ -151,7 +151,7 @@ phases:
- echo Logging in to Amazon ECR...
- aws --version
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AVALON_DOCKER_REPO
- (test -z "$AVALON_COMMIT" && AVALON_COMMIT=`git ls-remote $AVALON_REPO refs/heads/$AVALON_BRANCH | cut -f 1`) || true
- test -z "$AVALON_COMMIT" && AVALON_COMMIT=`git ls-remote $AVALON_REPO refs/heads/$AVALON_BRANCH | cut -f 1` || true
- AVALON_DOCKER_CACHE_TAG=$AVALON_COMMIT
- docker pull $AVALON_DOCKER_REPO:$AVALON_DOCKER_CACHE_TAG || docker pull $AVALON_DOCKER_REPO:latest || true
build:
@@ -165,7 +165,7 @@ phases:
- echo Pushing the Docker images...
- docker push $AVALON_DOCKER_REPO:$AVALON_COMMIT
- docker push $AVALON_DOCKER_REPO:latest
- aws ssm send-command --document-name "AWS-RunShellScript" --document-version "1" --targets '[{"Key":"InstanceIds","Values":["${aws_instance.compose.id}"]}]' --parameters '{"commands":["$(aws ecr get-login --region ${local.region} --no-include-email) && docker-compose pull && docker-compose up -d"],"workingDirectory":["/home/ec2-user/avalon-docker-aws_min"],"executionTimeout":["360"]}' --timeout-seconds 600 --max-concurrency "50" --max-errors "0" --cloud-watch-output-config '{"CloudWatchLogGroupName":"avalon-${var.environment}/ssm","CloudWatchOutputEnabled":true}' --region us-east-1
- aws ssm send-command --document-name "AWS-RunShellScript" --document-version "1" --targets '[{"Key":"InstanceIds","Values":["${aws_instance.compose.id}"]}]' --parameters '{"commands":["$(aws ecr get-login --region ${local.region} --no-include-email) && docker-compose pull && docker-compose up -d"],"workingDirectory":["/home/ec2-user/avalon-docker"],"executionTimeout":["360"]}' --timeout-seconds 600 --max-concurrency "50" --max-errors "0" --cloud-watch-output-config '{"CloudWatchLogGroupName":"avalon-${var.environment}/ssm","CloudWatchOutputEnabled":true}' --region us-east-1
BUILDSPEC
}

96 changes: 52 additions & 44 deletions compose.tf
Original file line number Diff line number Diff line change
@@ -36,6 +36,10 @@ data "aws_iam_policy_document" "compose" {
}
}

locals {
solr_data_device_name = "/dev/sdh"
}

resource "aws_iam_policy" "this_bucket_policy" {
name = "${local.namespace}-compose-bucket-access"
policy = data.aws_iam_policy_document.this_bucket_access.json
@@ -189,45 +193,51 @@ resource "aws_instance" "compose" {
}

user_data = base64encode(templatefile("scripts/compose-init.sh", {
ec2_public_key = "${var.ec2_public_key}"
solr_backups_efs_id = "${aws_efs_file_system.solr_backups.id}"
solr_backups_efs_dns_name = "${aws_efs_file_system.solr_backups.dns_name}"
db_fcrepo_address = "${module.db_fcrepo.db_instance_address}"
db_fcrepo_username = "${module.db_fcrepo.db_instance_username}"
db_fcrepo_password = "${module.db_fcrepo.db_instance_password}"
db_fcrepo_port = "${module.db_fcrepo.db_instance_port}"
db_avalon_address = "${module.db_avalon.db_instance_address}"
db_avalon_username = "${module.db_avalon.db_instance_username}"
db_avalon_password = "${module.db_avalon.db_instance_password}"
fcrepo_binary_bucket_access_key = "${length(var.fcrepo_binary_bucket_username) > 0 ? var.fcrepo_binary_bucket_access_key : values(aws_iam_access_key.fcrepo_bin_created_access)[0].id }"
fcrepo_binary_bucket_secret_key = "${length(var.fcrepo_binary_bucket_username) > 0 ? var.fcrepo_binary_bucket_secret_key : values(aws_iam_access_key.fcrepo_bin_created_access)[0].secret }"
fcrepo_binary_bucket_id = "${aws_s3_bucket.fcrepo_binary_bucket.id}"
compose_log_group_name = "${aws_cloudwatch_log_group.compose_log_group.name}"
fcrepo_db_ssl = "${var.fcrepo_db_ssl}"
derivatives_bucket_id = "${aws_s3_bucket.this_derivatives.id}"
masterfiles_bucket_id = "${aws_s3_bucket.this_masterfiles.id}"
preservation_bucket_id = "${aws_s3_bucket.this_preservation.id}"
supplemental_files_bucket_id = "${aws_s3_bucket.this_supplemental_files.id}"
avalon_ecr_repository_url = "${aws_ecr_repository.avalon.repository_url}"
avalon_repo = "${var.avalon_repo}"
redis_host_name = "${aws_route53_record.redis.name}"
aws_region = "${var.aws_region}"
avalon_fqdn = "${length(var.alt_hostname) > 0 ? values(var.alt_hostname)[0].hostname : aws_route53_record.alb.fqdn}"
streaming_fqdn = "${aws_route53_record.alb_streaming.fqdn}"
elastictranscoder_pipeline_id = "${aws_elastictranscoder_pipeline.this_pipeline.id}"
email_comments = "${var.email_comments}"
email_notification = "${var.email_notification}"
email_support = "${var.email_support}"
avalon_admin = "${var.avalon_admin}"
bib_retriever_protocol = "${var.bib_retriever_protocol}"
bib_retriever_url = "${var.bib_retriever_url}"
bib_retriever_query = "${var.bib_retriever_query}"
bib_retriever_host = "${var.bib_retriever_host}"
bib_retriever_port = "${var.bib_retriever_port}"
bib_retriever_database = "${var.bib_retriever_database}"
bib_retriever_attribute = "${var.bib_retriever_attribute}"
bib_retriever_class = "${var.bib_retriever_class}"
bib_retriever_class_require = "${var.bib_retriever_class_require}"
ec2_public_key = var.ec2_public_key
ec2_users = var.ec2_users
solr_data_device_name = local.solr_data_device_name
solr_backups_efs_id = aws_efs_file_system.solr_backups.id
solr_backups_efs_dns_name = aws_efs_file_system.solr_backups.dns_name
db_fcrepo_address = module.db_fcrepo.db_instance_address
db_fcrepo_username = module.db_fcrepo.db_instance_username
db_fcrepo_password = module.db_fcrepo.db_instance_password
db_fcrepo_port = module.db_fcrepo.db_instance_port
db_avalon_address = module.db_avalon.db_instance_address
db_avalon_username = module.db_avalon.db_instance_username
db_avalon_password = module.db_avalon.db_instance_password
fcrepo_binary_bucket_access_key = length(var.fcrepo_binary_bucket_username) > 0 ? var.fcrepo_binary_bucket_access_key : values(aws_iam_access_key.fcrepo_bin_created_access)[0].id
fcrepo_binary_bucket_secret_key = length(var.fcrepo_binary_bucket_username) > 0 ? var.fcrepo_binary_bucket_secret_key : values(aws_iam_access_key.fcrepo_bin_created_access)[0].secret
fcrepo_binary_bucket_id = aws_s3_bucket.fcrepo_binary_bucket.id
compose_log_group_name = aws_cloudwatch_log_group.compose_log_group.name
fcrepo_db_ssl = var.fcrepo_db_ssl
derivatives_bucket_id = aws_s3_bucket.this_derivatives.id
masterfiles_bucket_id = aws_s3_bucket.this_masterfiles.id
preservation_bucket_id = aws_s3_bucket.this_preservation.id
supplemental_files_bucket_id = aws_s3_bucket.this_supplemental_files.id
avalon_ecr_repository_url = aws_ecr_repository.avalon.repository_url
avalon_repo = var.avalon_repo
avalon_docker_code_repo = var.avalon_docker_code_repo
avalon_docker_code_branch = var.avalon_docker_code_branch
avalon_docker_code_commit = var.avalon_docker_code_commit
redis_host_name = aws_route53_record.redis.name
aws_region = var.aws_region
avalon_fqdn = length(var.alt_hostname) > 0 ? values(var.alt_hostname)[0].hostname : aws_route53_record.alb.fqdn
streaming_fqdn = aws_route53_record.alb_streaming.fqdn
elastictranscoder_pipeline_id = aws_elastictranscoder_pipeline.this_pipeline.id
email_comments = var.email_comments
email_notification = var.email_notification
email_support = var.email_support
avalon_admin = var.avalon_admin
bib_retriever_protocol = var.bib_retriever_protocol
bib_retriever_url = var.bib_retriever_url
bib_retriever_query = var.bib_retriever_query
bib_retriever_host = var.bib_retriever_host
bib_retriever_port = var.bib_retriever_port
bib_retriever_database = var.bib_retriever_database
bib_retriever_attribute = var.bib_retriever_attribute
bib_retriever_class = var.bib_retriever_class
bib_retriever_class_require = var.bib_retriever_class_require
extra_docker_environment_variables = var.extra_docker_environment_variables
}))

vpc_security_group_ids = [
@@ -236,16 +246,14 @@ resource "aws_instance" "compose" {
aws_security_group.public_ip.id,
]

lifecycle {
ignore_changes = [ami, user_data]
}
user_data_replace_on_change = true
}

resource "aws_route53_record" "ec2_hostname" {
zone_id = module.dns.public_zone_id
name = local.ec2_hostname
type = "A"
ttl = 300
ttl = 30
records = [aws_instance.compose.public_ip]
}

@@ -288,7 +296,7 @@ resource "aws_cloudwatch_log_group" "compose_log_group" {
}

resource "aws_volume_attachment" "compose_solr" {
device_name = "/dev/sdh"
device_name = local.solr_data_device_name
volume_id = aws_ebs_volume.solr_data.id
instance_id = aws_instance.compose.id
}
120 changes: 92 additions & 28 deletions scripts/compose-init.sh
Original file line number Diff line number Diff line change
@@ -1,37 +1,99 @@
#!/bin/bash
#
# Check the output from this script on the EC2 VM with:
#
# journalctl -u cloud-final
#

declare -r SOLR_DATA_DEVICE=${solr_data_device_name}

#
# Add and configure users.
#

# Add SSH public key if var was set
if [[ -n "${ec2_public_key}" ]]; then
# But first ensure existance and correct permissions
sudo -Hu ec2-user bash <<- EOF
umask 0077
mkdir -p /home/ec2-user/.ssh
touch /home/ec2-user/.ssh/authorized_keys
EOF
echo "${ec2_public_key}" >> /home/ec2-user/.ssh/authorized_keys
install -d -m 0755 -o ec2-user -g ec2-user ~ec2-user/.ssh
install -m 0644 -o ec2-user -g ec2-user /dev/null ~ec2-user/.ssh/authorized_keys
printf %s\\n "${ec2_public_key}" >>~ec2-user/.ssh/authorized_keys
fi

# Create filesystem only if there isn't one
if [[ ! `sudo file -s /dev/xvdh` == *"Linux"* ]]; then
sudo mkfs -t ext4 /dev/xvdh
groupadd --system docker

# Allow all users in the wheel group to run all commands without a password.
sed -i 's/^# \(%wheel\s\+ALL=(ALL)\s\+NOPASSWD: ALL$\)/\1/' /etc/sudoers

# The EC2 user's home directory can safely be made world-readable
chmod 0755 ~ec2-user

%{ for username, user_config in ec2_users ~}
useradd --comment "${user_config.gecos}" --groups adm,wheel,docker "${username}"
install -d -o "${username}" -g "${username}" ~${username}/.ssh
install -m 0644 -o "${username}" -g "${username}" \
/dev/null ~${username}/.ssh/authorized_keys
%{ for ssh_key in user_config.ssh_keys ~}
printf %s\\n "${ssh_key}" >>~${username}/.ssh/authorized_keys
%{ endfor ~}
%{ for setup_command in user_config.setup_commands ~}
${setup_command}
%{ endfor }
%{ endfor ~}

#
# Configure filesystems.
#

# Only format the Solr disk if it's blank.
blkid --probe "$SOLR_DATA_DEVICE" -o export | grep -qE "^PTTYPE=|^TYPE=" ||
mkfs -t ext4 "$SOLR_DATA_DEVICE"

install -d -m 0 /srv/solr_data
echo "$SOLR_DATA_DEVICE /srv/solr_data ext4 defaults 0 2" >>/etc/fstab
# If the mountpoint couldn't be mounted, leave it mode 0 so Solr will fail
# safely.
if mount /srv/solr_data; then
chown -R 8983:8983 /srv/solr_data
else
echo "Error: Could not mount solr_data EBS volume." >&2
fi

sudo mkdir /srv/solr_data
sudo mount /dev/xvdh /srv/solr_data
sudo chown -R 8983:8983 /srv/solr_data
sudo echo /dev/xvdh /srv/solr_data ext4 defaults,nofail 0 2 >> /etc/fstab

# Setup
echo '${solr_backups_efs_id}:/ /srv/solr_backups nfs nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport,_netdev 0 0' | sudo tee -a /etc/fstab
sudo mkdir -p /srv/solr_backups && sudo mount -t nfs -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport,_netdev ${solr_backups_efs_dns_name}:/ /srv/solr_backups
sudo chown 8983:8983 /srv/solr_backups
sudo yum install -y docker && sudo usermod -a -G docker ec2-user && sudo systemctl enable --now docker
sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

sudo wget https://github.com/avalonmediasystem/avalon-docker/archive/aws_min.zip -O /home/ec2-user/aws_min.zip && cd /home/ec2-user && unzip aws_min.zip
# Create .env file
sudo cat << EOF > /home/ec2-user/avalon-docker-aws_min/.env
install -d -m 0 /srv/solr_backups
echo '${solr_backups_efs_id}:/ /srv/solr_backups nfs _netdev 0 0' >>/etc/fstab
if mount /srv/solr_backups; then
chown -R 8983:8983 /srv/solr_backups
else
echo "Error: Could not mount solr_backups EFS volume." >&2
fi

#
# Install Avalon dependencies.
#

yum install -y docker
systemctl enable --now docker
usermod -a -G docker ec2-user

tmp=$(mktemp -d) || exit 1
curl -L "$(printf %s "https://github.com/docker/compose/releases/latest/" \
"download/docker-compose-$(uname -s)-$(uname -m)" )" \
-o "$tmp/docker-compose" &&
install -t /usr/local/bin "$tmp/docker-compose"
rm -rf -- "$tmp"
unset tmp

declare -r AVALON_DOCKER_CHECKOUT_NAME=%{ if avalon_docker_code_commit != "" }${avalon_docker_code_commit}%{ else }${avalon_docker_code_branch}%{ endif }
curl -L ${avalon_docker_code_repo}/archive/$AVALON_DOCKER_CHECKOUT_NAME.zip |
install -m 0644 -o ec2-user -g ec2-user /dev/stdin ~ec2-user/avalon-docker.zip &&
setpriv --reuid ec2-user --regid ec2-user --clear-groups -- \
unzip -d ~ec2-user ~ec2-user/avalon-docker.zip
mv ~ec2-user/avalon-docker-$AVALON_DOCKER_CHECKOUT_NAME ~ec2-user/avalon-docker

#
# Set up Avalon.
#

install -m 0600 -o ec2-user -g ec2-user \
/dev/stdin ~ec2-user/avalon-docker/.env <<EOF
FEDORA_OPTIONS=-Dfcrepo.postgresql.host=${db_fcrepo_address} -Dfcrepo.postgresql.username=${db_fcrepo_username} -Dfcrepo.postgresql.password=${db_fcrepo_password} -Dfcrepo.postgresql.port=${db_fcrepo_port} -Daws.accessKeyId=${fcrepo_binary_bucket_access_key} -Daws.secretKey=${fcrepo_binary_bucket_secret_key} -Daws.bucket=${fcrepo_binary_bucket_id}
FEDORA_LOGGROUP=${compose_log_group_name}/fedora.log
FEDORA_MODESHAPE_CONFIG=classpath:/config/jdbc-postgresql-s3/repository${fcrepo_db_ssl ? "-ssl" : ""}.json
@@ -48,7 +110,7 @@ AVALON_REPO=${avalon_repo}

DATABASE_URL=postgres://${db_avalon_username}:${db_avalon_password}@${db_avalon_address}/avalon
ELASTICACHE_HOST=${redis_host_name}
SECRET_KEY_BASE=112f7d33c8864e0ef22910b45014a1d7925693ef549850974631021864e2e67b16f44aa54a98008d62f6874360284d00bb29dc08c166197d043406b42190188a
SECRET_KEY_BASE=$(tr -dc 0-9A-Za-z </dev/random 2>&- | head -c 64)
AVALON_BRANCH=main
AWS_REGION=${aws_region}
RAILS_LOG_TO_STDOUT=true
@@ -81,5 +143,7 @@ SETTINGS__ACTIVE_STORAGE__SERVICE=amazon
SETTINGS__ACTIVE_STORAGE__BUCKET=${supplemental_files_bucket_id}

CDN_HOST=https://${avalon_fqdn}
%{ for key, value in extra_docker_environment_variables ~}
${key}=${value}
%{ endfor ~}
EOF
sudo chown -R ec2-user /home/ec2-user/avalon-docker-aws_min
19 changes: 18 additions & 1 deletion terraform.tfvars.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
environment = "dev"
#zone_prefix = ""
hosted_zone_name = "mydomain.org"
# At least one of ec2_keyname or ec2_public_key must be set
# At least one of 'ec2_keyname' or 'ec2_public_key', or 'ec2_users' must be set
# for you to have access to your EC2 instance.
#ec2_keyname = "my-ec2-key"
#ec2_public_key = ""
#ec2_users = [
# user = {
# ssh_keys = [
# "ssh-ed25519 ..."
# ]
# }
# another = {
# gecos = "Another User"
# ssh_keys = [
# "ssh-rsa ..."
# ]
# setup_commands = [
# "echo 'set editing-mode vi' | install -m 0644 -o another -g another /dev/stdin ~another/.inputrc"
# ]
# }
#]
stack_name = "mystack"
ssh_cidr_blocks = []
# If the user below is empty, Terraform will attempt to
34 changes: 32 additions & 2 deletions variables.tf
Original file line number Diff line number Diff line change
@@ -57,6 +57,21 @@ variable "avalon_commit" {
default = ""
}

variable "avalon_docker_code_repo" {
description = "The avalon-docker repository to pull when running docker-compose"
default = "https://github.com/avalonmediasystem/avalon-docker"
}

variable "avalon_docker_code_branch" {
description = "The avalon-docker branch to use when running docker-compose"
default = "aws_min"
}

variable "avalon_docker_code_commit" {
description = "The full avalon-docker commit hash to use when running docker-compose (empty defaults to most recent for the avalon_docker_code_branch)"
default = ""
}

variable "bib_retriever_protocol" {
default = "sru"
}
@@ -117,7 +132,7 @@ variable "db_fcrepo_username" {

variable "ec2_keyname" {
type = string
default = ""
default = null
description = "The name of an AWS EC2 key pair to use for authenticating"
}

@@ -127,6 +142,15 @@ variable "ec2_public_key" {
description = "A SSH public key string to use for authenticating"
}

variable "ec2_users" {
type = map(object({
gecos = optional(string, "")
ssh_keys = optional(list(string), [])
setup_commands = optional(list(string), [])
}))
default = {}
}

variable "email_comments" {
type = string
}
@@ -143,6 +167,12 @@ variable "environment" {
type = string
}

variable "extra_docker_environment_variables" {
description = "These are passed in to the compose-init.sh script"
type = map(string)
default = {}
}

variable "fcrepo_binary_bucket_username" {
type = string
default = ""
@@ -172,7 +202,7 @@ variable "hosted_zone_name" {
}

variable "postgres_version" {
default = "14.10"
default = "14.12"
}

#variable "sms_notification" {