Hello again! It’s been a while, because I finally finished my studied and I’m now Bachelor of Science :-). Anyway, I wanted to create a blog post of my bachelor thesis and this is going to be the one.
The topic of my thesis was how to speed up development performance when developing Flutter applications with cloud infrastructure. The infrastrcture was completely created with Terraform in AWS. The architecture itself is based on a sample published by AWS itself. It consists of:
- an internet gateway (for access from public of course)
- an application loadbalancer (which I replaced by an Elastic Loadbalancer)
- NAT gateways for network address translation of public to private IP adresses and
- the Jenkins leader as well as the agents of course
As this is of course the current state of the architecture, this can be changed anytime, because everything was completely created using Terraform.
This image shows a simplified view of the architecture itself. It’s created over two AZs (eu-central-1a and eu-central-1b) in this example. The AZs can be configured within the Terraform config. It’s even possible to distribute the infrastructure about more than only two AZs.
Provisioning of the cloud infrastructure
Link to heading
Let’s start with the provisioning of the AWS infrastructure. As I appreciate automation a lot I did not build any infrastructure by hand. Instead I used Terraform to create every necessary piece of infrastructure, from the VPC until the Jenkins leader. The most interesting parts as of now are the main file and the variables.
The content below is inside the main.tf
file. It basically created all necessary security groups, subnets as well as the Elastic Container Service.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| provider "aws" {
region = "eu-central-1"
}
# We need a cluster in which to put our service.
resource "aws_ecs_cluster" "JenkinsThesisAwsDev" {
name = var.application_name
}
# Log groups hold logs from our app.
resource "aws_cloudwatch_log_group" "JenkinsThesisAwsDev" {
name = "/ecs/${var.application_name}"
# Delete Logs after 7 days
retention_in_days = 7
# Write the environment into tags
tags = {
"Environment" = var.environment
}
}
# The main service.
resource "aws_ecs_service" "JenkinsThesisAwsDev" {
name = "ecs_service_${var.application_name}"
task_definition = aws_ecs_task_definition.jenkins_master.arn
cluster = aws_ecs_cluster.JenkinsThesisAwsDev.id
launch_type = "FARGATE"
# Require service version 1.4.0!
platform_version = "1.4.0"
desired_count = 1
# Register the master and the port in dns
service_registries {
registry_arn = aws_service_discovery_service.jenkins_master.arn
port = 50000
}
load_balancer {
target_group_arn = aws_lb_target_group.jenkins.arn
container_name = "jenkins_master"
container_port = 8080
}
network_configuration {
assign_public_ip = false
security_groups = [
aws_security_group.outbound.id,
aws_security_group.efs_jenkins_security_group.id,
aws_security_group.jenkins.id
]
subnets = [
aws_subnet.private[0].id,
aws_subnet.private[1].id,
]
}
}
|
The region is also configured within this file. As you can see, we run this on Fargate, because this is easier. The other option would be to use EC2 machines, but this is not necessary.
Some other important points are
- the configuration of the Fargate version 1.4.0, because otherwise you cannot mount storage into the containers
- the DNS registration of the master and the agent
- the configuration of the Elastic loadbalancer to redirect any incoming traffic to the Jenkins master container on TCP port 8080
The tags and variables are populated inside the variables.tf
file, as listed here:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| # Type of environment e. g. dev or prod
variable "environment" {
description = "The name to use for the environment, used in Names etc."
type = string
default = "dev"
}
# Name of the Application
variable "application_name" {
description = "The name of the application"
type = string
default = "jenkins_flutter_thesis"
}
# Output of the current region
data "aws_region" "current" {}
# Name of the Admin Account
variable "jenkins_accountname" {
description = "The Jenkins Master Account"
type = string
default = "developer"
}
# Create a random password for the first login of the administrator account.
resource "random_string" "jenkins_pass" {
length = 20
special = true
override_special = "/@\" "
}
variable "master_memory_amount" {
default = 1024
description = "Soft RAM Limit for Jenkins Agent"
}
variable "master_cpu_amount" {
default = 512
description = "Soft CPU Limit for Jenkins Agent"
}
variable "agent_memory_amount" {
default = 4096
description = "Soft RAM Limit for Jenkins Agent"
}
variable "agent_cpu_amount" {
default = 2048
description = "Soft CPU Limit for Jenkins Agent"
}
variable "s3_artifact_bucket_name" {
default = "jenkins-flutter-artifact-bucket"
description = "Default Name for S3 Bucket where Jenkins stores artifacts"
}
variable "s3_folder_prefix_name" {
default = "jenkins_artifacts"
description = "Default folder prefix for folder in the S3 Bucket where Jenkins stores artifacts"
}
|
Just to mention the most important points:
- Any tags as well as application name comes from this file, it can be configured accordingly
- the administrator’s username and password are configured inside this file. This is only for demo purposes, never do this in a production environment!
- CPU and memory limits are configured here
- Name and folder prefix for the S3 bucket, where artifacts are stored
There are of course even more Terraform files, like the ones that create IAM policies, ECS task definitions for Jenkins master and agent or an S3 bucket for storing artifcats.
The most interesting file is probably the network.tf
file, as it contains the most details about the network structure:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
| # network.tf
resource "aws_vpc" "app-vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.app-vpc.id
count = length(var.public_subnets)
cidr_block = var.public_subnets[count.index]
availability_zone = var.azs[count.index]
}
# Internet GW
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.app-vpc.id
tags = {
Name = var.application_name,
Environment = var.environment
}
}
resource "aws_route_table" "route" {
vpc_id = aws_vpc.app-vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
tags = {
Name = "Gatewayroute for ${var.application_name}: ${var.environment} environment"
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.route.id
count = length(var.public_subnets)
}
# Private Subnet
resource "aws_subnet" "private" {
vpc_id = aws_vpc.app-vpc.id
count = length(var.private_subnets)
cidr_block = var.private_subnets[count.index]
availability_zone = var.azs[count.index]
}
# NAT Stuff
# Elastic IP for NAT
resource "aws_eip" "nat" {
vpc = true
count = 2
}
resource "aws_nat_gateway" "ngw" {
subnet_id = aws_subnet.public[count.index].id
allocation_id = aws_eip.nat[count.index].id
count = length(var.public_subnets)
depends_on = [aws_internet_gateway.gw]
}
# Routing
resource "aws_route_table" "public" {
vpc_id = aws_vpc.app-vpc.id
count = length(var.public_subnets)
tags = {
"Name" = "Route Table for Public Subnet ${count.index}"
}
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.app-vpc.id
count = length(var.private_subnets)
tags = {
"Name" = "Route Table for Private Subnet ${count.index}"
}
}
# Routing Table Association
resource "aws_route_table_association" "public_subnet" {
subnet_id = aws_subnet.public[count.index].id
count = length(var.public_subnets)
route_table_id = aws_route_table.public[count.index].id
}
resource "aws_route_table_association" "private_subnet" {
subnet_id = aws_subnet.private[count.index].id
count = length(var.private_subnets)
route_table_id = aws_route_table.private[count.index].id
}
# Creating the Network Routes
resource "aws_route" "public_igw" {
count = length(var.public_subnets)
gateway_id = aws_internet_gateway.gw.id
route_table_id = aws_route_table.public[count.index].id
destination_cidr_block = "0.0.0.0/0"
}
resource "aws_route" "private_ngw" {
count = length(var.private_subnets)
nat_gateway_id = aws_nat_gateway.ngw[count.index].id
route_table_id = aws_route_table.private[count.index].id
destination_cidr_block = "0.0.0.0/0"
}
resource "aws_security_group" "https" {
name = "Incoming HTTP"
description = "HTTP and HTTPS traffic for ${var.environment} environment of ${var.application_name}"
vpc_id = aws_vpc.app-vpc.id
# HTTP
ingress {
from_port = 80
to_port = 80
protocol = "TCP"
cidr_blocks = ["0.0.0.0/0"]
}
# HTTPS
ingress {
from_port = 443
to_port = 443
protocol = "TCP"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
description = "Outbound TCP Connections to Jenkins Master"
from_port = 8080
protocol = "TCP"
to_port = 8080
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "jenkins" {
name = "Jenkins Master"
description = "Allows traffic to Jenkins Master."
vpc_id = aws_vpc.app-vpc.id
# HTTP Alternative
ingress {
from_port = 8080
to_port = 8080
protocol = "TCP"
cidr_blocks = ["0.0.0.0/0"]
security_groups = [aws_security_group.https.id]
}
ingress {
from_port = 50000
to_port = 50000
protocol = "TCP"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "jenkins_agent" {
name = "Jenkins Agents"
description = "Allows traffic to Jenkins Agents."
vpc_id = aws_vpc.app-vpc.id
# Allow Incoming Traffic on JLNP Port -> 50000
ingress {
description = "Allows JLNP Traffic"
from_port = 50000
protocol = "tcp"
self = true
to_port = 50000
}
tags = {
"Environment" = var.environment
"Application" = var.application_name
}
}
resource "aws_security_group" "outbound" {
name = "Outbound Traffic"
description = "Allow any outbound traffic for ${var.environment} environment of ${var.application_name}"
vpc_id = aws_vpc.app-vpc.id
# Any Outbound connections allowing
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# DNS Resolution for local zone
resource "aws_service_discovery_private_dns_namespace" "jenkins_zone" {
name = "jenkins.local"
description = "DNS Resolution for ${var.application_name}: ${var.environment} environment"
vpc = aws_vpc.app-vpc.id
}
# Load Balancer
resource "aws_lb_target_group" "jenkins" {
name = "Jenkins"
port = 8080
protocol = "HTTP"
target_type = "ip"
vpc_id = aws_vpc.app-vpc.id
deregistration_delay = 10
health_check {
enabled = true
path = "/login"
port = "8080"
}
depends_on = [aws_alb.jenkins]
}
resource "aws_alb" "jenkins" {
name = "Jenkins"
internal = false
load_balancer_type = "application"
subnets = [
aws_subnet.public[0].id,
aws_subnet.public[1].id,
]
security_groups = [
aws_security_group.https.id,
]
depends_on = [aws_internet_gateway.gw]
}
resource "aws_alb_listener" "jenkins_listener" {
load_balancer_arn = aws_alb.jenkins.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.jenkins.arn
}
}
resource "aws_security_group" "jenkins_agent" {
name = "Jenkins Agents"
description = "Allows traffic to Jenkins Agents."
vpc_id = aws_vpc.jenkins-vpc.id
# Allow Incoming Traffic on JLNP Port -> TCP 50000
ingress {
description = "Allows JLNP Traffic"
from_port = 50000
protocol = "tcp"
self = true
to_port = 50000
}
# Add Tags to the security group, coming from variables.tf
tags = {
"Environment" = var.environment
"Application" = var.application_name
}
}
|
As you can see this file contains a lot of information, what is created by Terraform if you apply the configuration. Long story short:
- create a new VPC, new public and private subets (according to the variables) and assign them routing tables
- assign Elastic IPs to the NAT gateways
- create security groups for incoming and outgoing traffic
- create a DNS service for name resolution
- start an ELB with listener within the two public subnets with a healthcheck
Now there are still some files missing like the Jenkins master file.
The Jenkins master controls every action done by the agents. If any task needs to be scheduled, the master will start a new ECS container agent. The master is configured like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
| # The task definition the Jenkins Master Container
resource "aws_ecs_task_definition" "jenkins_master" {
family = "JenkinsThesisAwsDev"
container_definitions = <<EOF
[
{
"name": "jenkins_master",
"image": "falconone/jenkins_thesis:latest",
"portMappings": [
{
"containerPort": 8080,
"hostPort": 8080
},
{
"containerPort": 50000,
"hostPort": 50000
}
],
"environment": [
{
"name": "EXECUTION_ROLE_ARN",
"value": "${aws_iam_role.JenkinsThesisAwsDev-task-execution-role.arn}"
},
{
"name": "SECURITY_GROUP_IDS",
"value": "${aws_security_group.jenkins_agent.id},${aws_security_group.outbound.id},${aws_security_group.efs_jenkins_security_group.id}"
},
{
"name": "AWS_REGION_NAME",
"value": "${data.aws_region.current.name}"
},
{
"name": "ECS_CLUSTER_NAME",
"value": "${aws_ecs_cluster.JenkinsThesisAwsDev.name}"
},
{
"name": "JENKINS_URL",
"value": "http://${aws_service_discovery_service.jenkins_master.name}.${aws_service_discovery_private_dns_namespace.jenkins_zone.name}:8080"
},
{
"name": "LOG_GROUP_NAME",
"value": "/ecs/${var.application_name}"
},
{
"name": "IMAGE_NAME",
"value": "falconone/jenkins-flutter:latest"
},
{
"name": "LOCAL_JENKINS_URL",
"value": ""
},
{
"name": "SUBNETS",
"value": "${aws_subnet.public.id}"
},
{
"name": "CPU_AMOUNT",
"value": "${var.agent_cpu_amount}"
},
{
"name": "MEMORY_AMOUNT",
"value": "${var.agent_memory_amount}"
},
{
"name": "PLATFORM_VERSION",
"value": "1.4.0"
},
{
"name": "BUCKET_NAME",
"value": "${var.s3_artifact_bucket_name}"
},
{
"name": "S3_FOLDER_PREFIX",
"value": "${var.s3_folder_prefix_name}"
},
{
"name": "JENKINS_ADMIN_USERNAME",
"value": "${var.jenkins_accountname}"
},
{
"name": "JENKINS_ADMIN_PASSWORD",
"value": "${random_string.jenkins_pass.result}"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-region": "eu-central-1",
"awslogs-group": "/ecs/${var.application_name}",
"awslogs-stream-prefix": "ecs"
}
}
}
]
EOF
# See here: https://stackoverflow.com/a/49947471
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role
execution_role_arn = aws_iam_role.JenkinsThesisAwsDev-task-execution-role.arn
task_role_arn = aws_iam_role.JenkinsThesisAwsDev-task-execution-role.arn
# Memory and CPU values for Jenkins Master, can be adjusted in variables.tf
cpu = var.master_memory_amount
memory = var.master_cpu_amount
requires_compatibilities = ["FARGATE"]
# requirement for AWS ECS Fargate containers
network_mode = "awsvpc"
depends_on = [aws_efs_mount_target.jenkins_master_home]
# Storage options for jenkins_home
volume {
name = "service-storage"
efs_volume_configuration {
file_system_id = aws_efs_file_system.jenkins_master_home.id
root_directory = "/var/jenkins_home"
transit_encryption = "ENABLED"
transit_encryption_port = 2999
authorization_config {
access_point_id = aws_efs_access_point.jenkins_master_home.id
# Necessary for other resources like S3, EFS or AWS SSM!
iam = "ENABLED"
}
}
}
}
# DNS Resolution for Jenkins Master
resource "aws_service_discovery_service" "jenkins_master" {
name = "master"
dns_config {
namespace_id = aws_service_discovery_private_dns_namespace.jenkins_zone.id
dns_records {
ttl = 60
type = "A"
}
dns_records {
ttl = 60
type = "SRV"
}
routing_policy = "MULTIVALUE"
}
}
|
There is also the storage.tf
file, which creates all storage related stuff, like the S3 bucket or an EFS access point to mount storage inside the agents:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
| resource "aws_security_group" "efs_jenkins_security_group" {
name = "efs_access"
description = "Allows efs access from jenkins master to efs storage on port 2049 for ${var.environment} environment."
vpc_id = aws_vpc.jenkins-vpc.id
# EFS default port
ingress {
description = "EFS access"
from_port = 2049
to_port = 2049
protocol = "tcp"
# security_groups = [aws_security_group.efs_jenkins_security_group.id]
# Self is required to allow access for this group on EFS storage
self = "true"
}
}
# Create the EFS Storage
resource "aws_efs_file_system" "jenkins_master_home" {
creation_token = "jenkins_master"
tags = {
Name = "jenkins_master"
environment = var.environment
}
}
# Create the EFS Mount Target
resource "aws_efs_mount_target" "jenkins_master_home" {
file_system_id = aws_efs_file_system.jenkins_master_home.id
subnet_id = aws_subnet.public.id
security_groups = [aws_security_group.efs_jenkins_security_group.id]
}
resource "aws_efs_access_point" "jenkins_master_home" {
file_system_id = aws_efs_file_system.jenkins_master_home.id
posix_user {
uid = 1000
gid = 1000
}
root_directory {
path = "/jenkins-home"
# Create the path with this rights, if it does not exist.
creation_info {
owner_gid = 1000
owner_uid = 1000
permissions = 755
}
}
}
# S3 Bucket for Artifact Storage
resource "aws_s3_bucket" "jenkins_artifact_storage" {
bucket = var.s3_artifact_bucket_name
acl = "private"
tags = {
Name = var.s3_artifact_bucket_name
Environment = var.environment
}
}
|
As the master starts an agent if necessary, the master needs the right to run new ECS containers. This and even more is configured inside the iam.tf
file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
| resource "aws_iam_role" "JenkinsThesisAwsDev-task-execution-role" {
name = "${var.application_name}-task-execution-role"
assume_role_policy = data.aws_iam_policy_document.ecs-task-assume-role.json
}
data "aws_iam_policy_document" "ecs-task-assume-role" {
statement {
actions = ["sts:AssumeRole"]
effect = "Allow"
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
data "aws_iam_policy" "ecs-task-execution-role" {
arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
# Attach the above policy to the execution role.
resource "aws_iam_role_policy_attachment" "ecs-task-execution-role-default" {
role = aws_iam_role.JenkinsThesisAwsDev-task-execution-role.name
policy_arn = data.aws_iam_policy.ecs-task-execution-role.arn
}
# Attach the required permissions to the Jenkins Task
resource "aws_iam_role_policy_attachment" "ecs-task-execution-role-jenkins" {
role = aws_iam_role.JenkinsThesisAwsDev-task-execution-role.name
policy_arn = aws_iam_policy.jenkins_agents.arn
}
# Data Policy for Jenkins Master to start new Jenkins Agents
# https://stackoverflow.com/questions/62831874/terrafrom-aws-iam-policy-document-condition-correct-syntax
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#condition
data "aws_iam_policy_document" "jenkins_master" {
statement {
actions = [
"ecs:RegisterTaskDefinition",
"ecs:ListClusters",
"ecs:DescribeContainerInstances",
"ecs:ListTaskDefinitions",
"ecs:DescribeTaskDefinition",
"ecs:DeregisterTaskDefinition",
]
resources = ["*"]
effect = "Allow"
}
# Listing of Container Instances
statement {
actions = ["ecs:ListContainerInstances"]
effect = "Allow"
resources = [aws_ecs_cluster.JenkinsThesisAwsDev.arn]
}
# Run Tasks in ECS
statement {
actions = ["ecs:RunTask"]
effect = "Allow"
resources = ["arn:aws:ecs:${data.aws_region.current.name}:526531137161:task-definition/*"]
condition {
test = "ArnEquals"
variable = "ecs:cluster"
values = [
aws_ecs_cluster.JenkinsThesisAwsDev.arn
]
}
}
statement {
actions = ["ecs:StopTask"]
effect = "Allow"
resources = [
"arn:aws:ecs:*:*:task/*",
"arn:aws:ecs:${data.aws_region.current.name}:526531137161:task/*"
]
condition {
test = "ArnEquals"
variable = "ecs:cluster"
values = [
aws_ecs_cluster.JenkinsThesisAwsDev.arn
]
}
}
statement {
actions = ["ecs:DescribeTasks"]
effect = "Allow"
resources = [
"arn:aws:ecs:*:*:task/*",
"arn:aws:ecs:${data.aws_region.current.name}:526531137161:task/*"
]
condition {
test = "ArnEquals"
variable = "ecs:cluster"
values = [
aws_ecs_cluster.JenkinsThesisAwsDev.arn
]
}
}
statement {
actions = ["iam:GetRole", "iam:PassRole"]
effect = "Allow"
resources = [aws_iam_role.JenkinsThesisAwsDev-task-execution-role.arn]
}
# S3 Bucket Related policys for storing build artifacts
statement {
actions = [
"s3:ListBucket"
]
effect = "Allow"
sid = "AllowListingOfFolder"
resources = [aws_s3_bucket.jenkins_artifact_storage.arn]
condition {
test = "StringLike"
variable = "s3:prefix"
values = ["${var.s3_folder_prefix_name}/*"]
}
}
# Allow the listing of bucket locations.
statement {
actions = [
"s3:GetBucketLocation"
]
effect = "Allow"
sid = "AllowListingOfBuckets"
resources = [aws_s3_bucket.jenkins_artifact_storage.arn]
}
statement {
sid = "AllowS3ActionsInFolder"
effect = "Allow"
actions = [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket",
]
resources = ["${aws_s3_bucket.jenkins_artifact_storage.arn}/${var.s3_folder_prefix_name}/*"]
}
}
# Policy for Jenkins Master to start new Jenkins Agents
# https://stackoverflow.com/questions/62831874/terrafrom-aws-iam-policy-document-condition-correct-syntax
resource "aws_iam_policy" "jenkins_agents" {
description = "Allows the Jenkins master to start new agents."
name = "${var.application_name}_ecs_policy"
# Policy
# Hint: Curly braces may not be indented otherwise Terraform fails
policy = data.aws_iam_policy_document.jenkins_master.json
}
|
With everything of the files listed above the necessary AWS infrastructure is created within a few minutes. No need to click or anything else, you just have to run a terraform apply
from the commandline. As it’s hard to get all the code inside this document, I created a new repository on Github, where I’ll upload all the required code.
The cool thing about all the stuff above is, it created everthing and from now on we only have to worry about the container setup. The container setup I’m going to cover in part 2 of the series.
If you have any questions, do not hesitate to contact me! I hope you enjoyed reading this post and we will see each other in part 2, where we talk about how to configure the Jenkins master and agent.
See more in part 2 of the series.