Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
065b076
Add reuploader builder task to dev
aagbsn Jun 8, 2026
ccda78d
fix trigger path
aagbsn Jun 8, 2026
01ab568
Extend ooniapi_service to provide scheduled run
aagbsn Jun 8, 2026
e8e191e
add scheduled_service module
aagbsn Jun 9, 2026
1d82bdb
add reuploader scheduled service (hourly)
aagbsn Jun 9, 2026
d245b73
set failed reports bucket
aagbsn Jun 9, 2026
4dd954e
reuploader: set DRY_RUN=true
aagbsn Jun 11, 2026
b6e2ba7
reuploader: set BATCH_SIZE=10
aagbsn Jun 11, 2026
0b94e88
reuploader: set AWS_SECRET_ACCESS_KEY from module
aagbsn Jun 11, 2026
22f35d5
reuploader: set scheduled_task_cluster
aagbsn Jun 11, 2026
3a0b895
reuploader: remove unused outputs
aagbsn Jun 11, 2026
d171d28
reuploader: add first_run to create container definition
aagbsn Jun 11, 2026
c2ffbf1
reuploader: pin to tagged container
aagbsn Jun 11, 2026
6964ab5
Merge remote-tracking branch 'origin/main' into add_reuploader_builder
aagbsn Jun 11, 2026
d55b502
remove redundant count
aagbsn Jun 11, 2026
d9361d9
Merge remote-tracking branch 'origin/main' into add_reuploader_builder
aagbsn Jun 11, 2026
dbe872f
singleton requires no index
aagbsn Jun 11, 2026
27f9c59
FIXME: try to add events:PutRule et al to profile
aagbsn Jun 11, 2026
093c98f
Add permission to the ooni_devops role to modify events
LDiazN Jun 11, 2026
94f3638
unmix environment from secrets
aagbsn Jun 11, 2026
92fb38a
use bucket from https://github.com/ooni/devops/issues/398
aagbsn Jun 11, 2026
bafa175
update reuploader, fix env
aagbsn Jun 11, 2026
ed341e5
add AWS_REGION to task_environment
aagbsn Jun 11, 2026
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
55 changes: 55 additions & 0 deletions tf/environments/dev/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,61 @@ module "fastpath_builder" {
codepipeline_bucket = aws_s3_bucket.ooniapi_codepipeline_bucket.bucket
}

module "reuploader_builder" {
source = "../../modules/ooni_docker_build"
trigger_tag = ""

service_name = "reuploader"
repo = "ooni/backend"
branch_name = "add_fastpath_reuploader"
environment = local.environment
buildspec_path = "reuploader/buildspec.yml"
trigger_path = "reuploader/**"
codestar_connection_arn = aws_codestarconnections_connection.oonidevops.arn

codepipeline_bucket = aws_s3_bucket.ooniapi_codepipeline_bucket.bucket
}

module "reuploader" {
source = "../../modules/scheduled_service"

task_memory = 256

vpc_id = module.network.vpc_id

first_run = true
service_name = "reuploader"
default_docker_image_url = "ooni/reuploader:20260611-840e1b63"
schedule_expression = "cron(0/5 * * * ? 2000-2199)"
stage = local.environment
dns_zone_ooni_io = local.dns_zone_ooni_io
key_name = module.adm_iam_roles.oonidevops_key_name
scheduled_task_cluster = module.ooniapi_cluster.cluster_name
ecs_cluster_id = module.ooniapi_cluster.cluster_id

task_environment = {
AWS_REGION = var.aws_region
BATCH_SIZE = 10
BUCKET_NAME = "ooniprobe-failed-reports-eu-central-1-1d24426a"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use the terraform variable for this

aws_s3_bucket.ooniprobe_failed_reports.bucket

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm. the default behavior is to createBucket and fail

DRY_RUN = true
FASTPATH_API = "http://${local.fastpath_hosts[length(local.fastpath_hosts) - 1]}:8472"
}

task_secrets = {
AWS_SECRET_ACCESS_KEY = module.ooniapi_user.aws_secret_access_key_arn
AWS_ACCESS_KEY_ID = module.ooniapi_user.aws_access_key_id_arn
}

ooniapi_service_security_groups = [
module.ooniapi_cluster.web_security_group_id
]

tags = merge(
local.tags,
{ Name = "ooni-tier0-reuploader" }
)
}

#### OONI Run service

module "ooniapi_oonirun_deployer" {
Expand Down
3 changes: 2 additions & 1 deletion tf/modules/adm_iam_roles/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ resource "aws_iam_policy" "oonidevops" {
"secretsmanager:*",
"cloudhsm:*",
"athena:*",
"glue:*"
"glue:*",
"events:*"
],
"Resource": "*"
}
Expand Down
68 changes: 67 additions & 1 deletion tf/modules/ooniapi_service/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,72 @@ resource "aws_iam_role_policy" "ooniapi_service_task" {
policy = templatefile("${path.module}/templates/profile_policy.json", {})
}

resource "aws_iam_role" "events_run_task" {
count = var.run_on_schedule ? 1 : 0
name = "${local.name}-events-run-task-role"

assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "events.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}
EOF

tags = var.tags
}

resource "aws_iam_role_policy" "events_run_task_policy" {
count = var.run_on_schedule ? 1 : 0
name = "${local.name}-events-run-task-policy"
role = aws_iam_role.events_run_task[0].id

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ecs:RunTask",
"iam:PassRole",
"ecs:StartTask",
"ecs:DescribeClusters",
"ecs:DescribeTasks"
]
Resource = "*"
}
]
})
}

resource "aws_cloudwatch_event_rule" "scheduled_run" {
count = var.run_on_schedule ? 1 : 0
name = "${local.name}-schedule"
schedule_expression = var.schedule_expression
tags = var.tags
}

resource "aws_cloudwatch_event_target" "run_ecs_task" {
count = var.run_on_schedule ? 1 : 0
rule = aws_cloudwatch_event_rule.scheduled_run[0].name
arn = data.aws_ecs_cluster.target[0].arn

role_arn = aws_iam_role.events_run_task[0].arn

ecs_target {
task_definition_arn = aws_ecs_task_definition.ooniapi_service.arn
task_count = 1
}
}

data "aws_ecs_cluster" "target" {
count = var.run_on_schedule ? 1 : 0
cluster_name = var.scheduled_task_cluster
}

resource "aws_cloudwatch_log_group" "ooniapi_service" {
name = "ooni-ecs-group/${local.name}"
}
Expand Down Expand Up @@ -99,7 +165,7 @@ resource "aws_ecs_service" "ooniapi_service" {
name = local.name
cluster = var.ecs_cluster_id
task_definition = aws_ecs_task_definition.ooniapi_service.arn
desired_count = var.service_desired_count
desired_count = var.run_on_schedule ? 0 : var.service_desired_count

deployment_minimum_healthy_percent = 50
deployment_maximum_percent = 200
Expand Down
22 changes: 22 additions & 0 deletions tf/modules/ooniapi_service/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,25 @@ variable "autoscale_policies" {

default = []
}

variable "run_on_schedule" {
type = bool
default = false

validation {
condition = !(var.run_on_schedule == true && var.scheduled_task_cluster == null)
error_message = "scheduled_task_cluster must be set when run_on_schedule = true."
}
}

variable "schedule_expression" {
type = string
default = "cron(0 6 ? * MON-FRI *)" # example default; callers override
}

variable "scheduled_task_cluster" {
type = string
description = "Name of the ECS cluster to run the scheduled task on (required when run_on_schedule = True)."
nullable = true
default = null
}
154 changes: 154 additions & 0 deletions tf/modules/scheduled_service/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
locals {
name = "scheduled-service-${var.service_name}"
# We construct a stripped name that is without the "ooni" substring and all
# vocals are stripped.
stripped_name = replace(replace(var.service_name, "ooni", ""), "[aeiou]", "")
# Short prefix should be less than 5 characters
short_prefix = "O${substr(local.stripped_name, 0, 3)}"
}

resource "aws_iam_role" "scheduled_service_task" {
name = "${local.name}-task-role"

tags = var.tags

assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}

resource "aws_iam_role_policy" "scheduled_service_task" {
name = "${local.name}-task-role"
role = aws_iam_role.scheduled_service_task.name

policy = templatefile("${path.module}/templates/profile_policy.json", {})
}

resource "aws_iam_role" "events_run_task" {
name = "${local.name}-events-run-task-role"

assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "events.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}
EOF

tags = var.tags
}

resource "aws_iam_role_policy" "events_run_task_policy" {
name = "${local.name}-events-run-task-policy"
role = aws_iam_role.events_run_task.id

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ecs:RunTask",
"iam:PassRole",
"ecs:StartTask",
"ecs:DescribeClusters",
"ecs:DescribeTasks",
"events:TagResource",
"events:PutRule",
"events:PutTargets",
]
Resource = "*"
}
]
})
}

resource "aws_cloudwatch_event_rule" "scheduled_run" {
name = "${local.name}-schedule"
schedule_expression = var.schedule_expression
tags = var.tags
}

resource "aws_cloudwatch_event_target" "run_ecs_task" {
rule = aws_cloudwatch_event_rule.scheduled_run.name
arn = data.aws_ecs_cluster.target.arn

role_arn = aws_iam_role.events_run_task.arn

ecs_target {
task_definition_arn = aws_ecs_task_definition.scheduled_service.arn
task_count = 1
}
}

data "aws_ecs_cluster" "target" {
cluster_name = var.scheduled_task_cluster
}

resource "aws_cloudwatch_log_group" "scheduled_service" {
name = "ooni-ecs-group/${local.name}"
}

// This is done to retrieve the image name of the current task definition
// It's important to keep aligned the container_name and task_definitions
data "aws_ecs_container_definition" "scheduled_service_current" {
task_definition = "${local.name}-td"
container_name = local.name
count = var.first_run ? 0 : 1
}

resource "aws_ecs_task_definition" "scheduled_service" {
family = "${local.name}-td"
network_mode = "bridge"

container_definitions = jsonencode([
{
memoryReservation = var.task_memory,
memory = var.memory_hard_limit
essential = true,
image = try(
data.aws_ecs_container_definition.scheduled_service_current[0].image,
var.default_docker_image_url
),
name = local.name,

environment = [
for k, v in var.task_environment : {
name = k,
value = v
}
],
secrets = [
for k, v in var.task_secrets : {
name = k,
valueFrom = v
}
],
logConfiguration = {
logDriver = "awslogs",
options = {
awslogs-group = aws_cloudwatch_log_group.scheduled_service.name,
awslogs-region = var.aws_region
}
}
}
])
execution_role_arn = aws_iam_role.scheduled_service_task.arn
tags = var.tags
track_latest = true
}
Empty file.
Loading
Loading