diff --git a/.github/workflows/ecs_deploy.yml b/.github/workflows/ecs_deploy.yml index cb84b373..4e044d7e 100644 --- a/.github/workflows/ecs_deploy.yml +++ b/.github/workflows/ecs_deploy.yml @@ -2,7 +2,7 @@ name: Deploy to Amazon ECS on: push: - branches: [ "main" ] + branches: [ "test" ] env: AWS_REGION: ${{ secrets.AWS_REGION }} @@ -33,6 +33,22 @@ jobs: submodules: 'recursive' token: ${{ secrets.GH_ISSUEFY_ROY_TOKEN }} + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + + - name: Terraform Init + run: terraform init + + - name: Terraform Apply + run: terraform apply -auto-approve + - name: Set up Node.js uses: actions/setup-node@v4 with: @@ -72,13 +88,6 @@ jobs: chmod +x gradlew ./gradlew clean build - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 diff --git a/.github/workflows/terraform_plan.yml b/.github/workflows/terraform_plan.yml new file mode 100644 index 00000000..78da4370 --- /dev/null +++ b/.github/workflows/terraform_plan.yml @@ -0,0 +1,97 @@ +name: Terraform Plan on PR + +on: + pull_request: + branches: + - test + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + terraform-plan: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Set up Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: 1.6.6 + terraform_wrapper: false + + - name: Terraform Init + run: terraform init + working-directory: ./terraform + + - name: Terraform Plan and Capture Output + id: plan + working-directory: ./terraform + run: | + set +e + terraform plan -no-color -detailed-exitcode > >(tee plan_full.txt) 2> >(tee plan_full.txt >&2) + exit_code=$? + set -e + + resource_summary=$(grep -E '^(# | [~+\-])' plan_full.txt | head -n 50) + + plan_summary=$(grep -E '^Plan:' plan_full.txt) + + echo "summary_output<> $GITHUB_OUTPUT + echo "$resource_summary" >> $GITHUB_OUTPUT + echo "$plan_summary" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + if [ "$exit_code" -eq 0 ]; then + echo "βœ… Terraform plan completed with no changes." + echo "plan_status=success" >> $GITHUB_OUTPUT + elif [ "$exit_code" -eq 2 ]; then + echo "βœ… Terraform plan completed with changes." + echo "plan_status=changes" >> $GITHUB_OUTPUT + elif [ "$exit_code" -eq 1 ]; then + echo "❌ Terraform plan failed with errors." + echo "plan_status=error" >> $GITHUB_OUTPUT + fi + + exit 0 + + - name: Upload full plan as artifact + uses: actions/upload-artifact@v4 + with: + name: terraform-plan + path: ./terraform/plan_full.txt + + - name: Comment PR with Plan Summary + uses: peter-evans/create-or-update-comment@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## πŸ“„ Terraform Plan Summary + + ### πŸ’¬ λ³€κ²½ μš”μ•½ + ```terraform + ${{ steps.plan.outputs.summary_output }} + ``` + + ### πŸ“Œ μƒνƒœ + ${{ steps.plan.outputs.plan_status == 'error' && '❌ **μ‹€νŒ¨**: Terraform Plan 도쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.' || '' }} + ${{ steps.plan.outputs.plan_status == 'changes' && 'πŸ”„ **변경사항 있음**: 적용 μ‹œ λ¦¬μ†ŒμŠ€ 변경이 λ°œμƒν•©λ‹ˆλ‹€.' || '' }} + ${{ steps.plan.outputs.plan_status == 'success' && 'βœ… **λ³€κ²½ μ—†μŒ**: ν˜„μž¬ 인프라 μƒνƒœλŠ” μ΅œμ‹ μž…λ‹ˆλ‹€.' || '' }} + + ### πŸ“Ž μ°Έκ³  + 전체 Terraform Plan κ²°κ³ΌλŠ” [GitHub Actions Artifact](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})μ—μ„œ ν™•μΈν•˜μ‹€ 수 μžˆμŠ΅λ‹ˆλ‹€. + + - name: Fail job if plan failed + if: ${{ steps.plan.outputs.plan_status == 'error' }} + run: exit 1 diff --git a/.gitignore b/.gitignore index 9b69782d..032401e2 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,8 @@ out/ /config.yml /src/main/resources/logback.xml docker-compose.yml + +### terraform ### +.terraform/ +*.tfstate +*.tfstate.* diff --git a/src/main/java/site/iris/issuefy/model/dto/LokiQueryAddRepositoryDto.java b/src/main/java/site/iris/issuefy/model/dto/LokiQueryAddRepositoryDto.java index 2bc2510a..8d0dcc46 100644 --- a/src/main/java/site/iris/issuefy/model/dto/LokiQueryAddRepositoryDto.java +++ b/src/main/java/site/iris/issuefy/model/dto/LokiQueryAddRepositoryDto.java @@ -8,9 +8,9 @@ @Data public class LokiQueryAddRepositoryDto { - private String addRepositoryCount = "0"; private static final int COUNT_INDEX = 1; private static final int FIRST_RESULT_INDEX = 0; + private String addRepositoryCount = "0"; @JsonProperty("data") private void unpackData(Data data) { diff --git a/src/main/resources/static/docs/api-guide.html b/src/main/resources/static/docs/api-guide.html index 014ca62d..6e34741e 100644 --- a/src/main/resources/static/docs/api-guide.html +++ b/src/main/resources/static/docs/api-guide.html @@ -1,516 +1,2187 @@ - - - - - -Issuefy API λͺ…μ„Έμ„œ - - - - + + + + + + Issuefy API λͺ…μ„Έμ„œ + + + +
-
-

1. μ‚¬μš©μž 인증/인가

-
-
-

1.1 OAuth 둜그인

-
-

HTTP μš”μ²­

-
-
+
+

1. μ‚¬μš©μž 인증/인가

+
+
+

1.1 OAuth 둜그인

+
+

HTTP μš”μ²­

+
+
GET /api/login?code=test-auth-code HTTP/1.1
 Accept: application/json
 Host: issuefy.site
-
-
-
-
-

HTTP 응닡

-
-
+
+
+
+
+

HTTP 응닡

+
+
HTTP/1.1 200 OK
 Vary: Origin
 Vary: Access-Control-Request-Method
@@ -528,159 +2199,178 @@ 

HTTP 응닡

"refreshToken" : "test-refresh-token" } }
-
-
-
-
응닡 ν•„λ“œ
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription

userName

String

μ‚¬μš©μž 둜그인 이름

userEmail

String

μ‚¬μš©μž 이메일 μ£Όμ†Œ

avatarURL

String

μ‚¬μš©μž 아바타 URL

alertStatus

Boolean

μ•Œλ¦Ό μƒνƒœ

jwt.accessToken

String

JWT μ•‘μ„ΈμŠ€ 토큰

jwt.refreshToken

String

JWT λ¦¬ν”„λ ˆμ‹œ 토큰

-
-
-
-
-

1.2 OAuth λ‘œκ·Έμ•„μ›ƒ

-
-

HTTP μš”μ²­

-
-
+
+
+
+
응닡 ν•„λ“œ
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

+ userName

String +

μ‚¬μš©μž 둜그인 이름

+ userEmail

String +

μ‚¬μš©μž 이메일 μ£Όμ†Œ

+ avatarURL

String +

μ‚¬μš©μž 아바타 URL

+ alertStatus

Boolean +

μ•Œλ¦Ό μƒνƒœ

jwt.accessToken +

String +

JWT μ•‘μ„ΈμŠ€ 토큰

jwt.refreshToken +

String +

JWT λ¦¬ν”„λ ˆμ‹œ 토큰

+
+
+
+
+

1.2 OAuth λ‘œκ·Έμ•„μ›ƒ

+
+

HTTP μš”μ²­

+
+
POST /api/logout HTTP/1.1
 Authorization: Bearer test-jwt-token
 Host: issuefy.site
 Content-Type: application/x-www-form-urlencoded
-
-
-
-
μš”μ²­ 헀더
- ---- - - - - - - - - - - - - -
NameDescription

Authorization

JWT 토큰

-
-
-
-

HTTP 응닡

-
-
+
+
+
+
μš”μ²­ 헀더
+ + + + + + + + + + + + + + + + + +
NameDescription

+ Authorization

JWT 토큰

+
+
+
+

HTTP 응닡

+
+
HTTP/1.1 200 OK
 Vary: Origin
 Vary: Access-Control-Request-Method
 Vary: Access-Control-Request-Headers
-
-
-
-
-
-
- +
+

2. 리포지토리

+
+
+

2.1 ꡬ독쀑인 리포지토리 λͺ©λ‘ 쑰회 +

+
+

HTTP μš”μ²­

+
+
GET /api/subscriptions?page=0&sort=latestUpdateAt&order=desc&starred=false HTTP/1.1
 Authorization: Bearer testToken
 Host: issuefy.site
-
-
-
-
쿼리 λ§€κ°œλ³€μˆ˜
- ---- - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterDescription

page

νŽ˜μ΄μ§€ 번호

sort

μ •λ ¬ κΈ°μ€€

order

μ •λ ¬ μˆœμ„œ (asc/desc)

starred

즐겨찾기 μ—¬λΆ€

-
-
-
-

HTTP 응닡

-
-
+
+
+
+
쿼리 λ§€κ°œλ³€μˆ˜
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterDescription

page +

νŽ˜μ΄μ§€ 번호

sort +

μ •λ ¬ κΈ°μ€€

order +

μ •λ ¬ μˆœμ„œ (asc/desc)

+

starred +

즐겨찾기 μ—¬λΆ€

+
+
+
+

HTTP 응닡

+
+
HTTP/1.1 200 OK
 Vary: Origin
 Vary: Access-Control-Request-Method
@@ -702,16 +2392,16 @@ 

HTTP 응닡

-
-
-
-
- +
+

2.2 리포지토리 ꡬ독

+
+

HTTP μš”μ²­

+
+
POST /api/subscriptions HTTP/1.1
 Content-Type: application/json;charset=UTF-8
 Content-Length: 61
@@ -720,217 +2410,230 @@ 

HTTP μš”μ²­

-
-
-
-
μš”μ²­ λ³Έλ¬Έ
- ----- - - - - - - - - - - - - - - -
PathTypeDescription

repositoryUrl

String

GitHub 리포지토리 URL

-
-
-
-

HTTP 응닡

-
-
+
+
+
+
μš”μ²­ λ³Έλ¬Έ
+ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

+ repositoryUrl

String +

GitHub 리포지토리 URL

+
+
+
+
+

HTTP 응닡

+
+
HTTP/1.1 201 Created
 Vary: Origin
 Vary: Access-Control-Request-Method
 Vary: Access-Control-Request-Headers
 Location: https://github.com/2024-Iris/issuefy-spring
-
-
-
-
- +
+

2.3 리포지토리 ꡬ독 μ·¨μ†Œ

+
+

HTTP μš”μ²­

+
+
DELETE /api/subscriptions/1 HTTP/1.1
 Host: issuefy.site
-
-
-
-
경둜 λ§€κ°œλ³€μˆ˜
- - ---- - - - - - - - - - - - - -
Table 1. /api/subscriptions/{gh_repo_id}
ParameterDescription

gh_repo_id

GitHub 리포지토리 ID

-
-
-
-

HTTP 응닡

-
-
+
+
+
+
경둜 λ§€κ°œλ³€μˆ˜
+ + + + + + + + + + + + + + + + + + +
Table 1. /api/subscriptions/{gh_repo_id}
ParameterDescription

+ gh_repo_id

GitHub 리포지토리 ID

+
+
+
+
+

HTTP 응닡

+
+
HTTP/1.1 204 No Content
 Vary: Origin
 Vary: Access-Control-Request-Method
 Vary: Access-Control-Request-Headers
-
-
-
-
- +
+

2.4 리포지토리 즐겨찾기

+
+

HTTP μš”μ²­

+
+
PUT /api/subscriptions/star/1 HTTP/1.1
 Host: issuefy.site
 Content-Type: application/x-www-form-urlencoded
-
-
-
-
경둜 λ§€κ°œλ³€μˆ˜
- - ---- - - - - - - - - - - - - -
Table 2. /api/subscriptions/star/{gh_repo_id}
ParameterDescription

gh_repo_id

GitHub 리포지토리 ID

-
-
-
-

HTTP 응닡

-
-
+
+
+
+
경둜 λ§€κ°œλ³€μˆ˜
+ + + + + + + + + + + + + + + + + + +
Table 2. /api/subscriptions/star/{gh_repo_id}
ParameterDescription

+ gh_repo_id

GitHub 리포지토리 ID

+
+
+
+
+

HTTP 응닡

+
+
HTTP/1.1 204 No Content
 Vary: Origin
 Vary: Access-Control-Request-Method
 Vary: Access-Control-Request-Headers
-
-
-
-
-
-
- +
+

3. 이슈

+
+
+

3.1 이슈 λͺ©λ‘ 쑰회

+
+

HTTP μš”μ²­

+
+
GET /api/subscriptions/iris/issuefy/issues?page=0&size=15&sort=createdAt&order=desc HTTP/1.1
 Accept: application/json
 Host: issuefy.site
-
-
-
-
경둜 λ§€κ°œλ³€μˆ˜
- - ---- - - - - - - - - - - - - - - - - -
Table 3. /api/subscriptions/{org_name}/{repo_name}/issues
ParameterDescription

org_name

쑰직 이름

repo_name

리포지토리 이름

-
-
-
쿼리 λ§€κ°œλ³€μˆ˜
- ---- - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterDescription

page

νŽ˜μ΄μ§€ 번호

size

νŽ˜μ΄μ§€ 크기

sort

μ •λ ¬ κΈ°μ€€

order

μ •λ ¬ μˆœμ„œ

-
-
-
-

HTTP 응닡

-
-
+
+
+
+
경둜 λ§€κ°œλ³€μˆ˜
+ + + + + + + + + + + + + + + + + + + + + + +
Table 3. /api/subscriptions/{org_name}/{repo_name}/issues
ParameterDescription

+ org_name

쑰직 이름

+ repo_name

리포지토리 이름

+
+
+
쿼리 λ§€κ°œλ³€μˆ˜
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterDescription

page +

νŽ˜μ΄μ§€ 번호

size +

νŽ˜μ΄μ§€ 크기

sort +

μ •λ ¬ κΈ°μ€€

order +

μ •λ ¬ μˆœμ„œ

+
+
+
+

HTTP 응닡

+
+
HTTP/1.1 200 OK
 Vary: Origin
 Vary: Access-Control-Request-Method
@@ -960,12 +2663,12 @@ 

HTTP 응닡

-
-
-
-
응닡 λ³Έλ¬Έ
-
-
+
+
+
+
응닡 λ³Έλ¬Έ
+
+
{
   "currentPage" : 0,
   "pageSize" : 15,
@@ -988,111 +2691,115 @@ 
응닡 λ³Έλ¬Έ "starred" : false } ] }
-
-
-
-
-
-
-
- +
+
+

4. Server-Sent Events + (SSE)

+
+
+

4.1 SSE μ—°κ²°

+
+

HTTP μš”μ²­

+
+
GET /api/connect HTTP/1.1
 Accept: text/event-stream
 Host: issuefy.site
-
-
-
-
-

HTTP 응닡

-
-
+
+
+
+
+

HTTP 응닡

+
+
HTTP/1.1 200 OK
 Vary: Origin
 Vary: Access-Control-Request-Method
 Vary: Access-Control-Request-Headers
-
-
-
-
- +
+

4.2 SSE μˆ˜μ‹ 

+
+

HTTP μš”μ²­

+
+
POST /api/receive HTTP/1.1
 Content-Type: application/json;charset=UTF-8
 Content-Length: 34
 Host: issuefy.site
 
 {"updatedRepositoryIds":["1","2"]}
-
-
-
-
μš”μ²­ ν•„λ“œ
- ----- - - - - - - - - - - - - - - -
PathTypeDescription

updatedRepositoryIds

Array

μ—…λ°μ΄νŠΈ 된 리포지토리 ID λͺ©λ‘

-
-
-
-

HTTP 응닡

-
-
+
+
+
+
μš”μ²­ ν•„λ“œ
+ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

updatedRepositoryIds +

Array +

μ—…λ°μ΄νŠΈ 된 리포지토리 ID + λͺ©λ‘

+
+
+
+

HTTP 응닡

+
+
HTTP/1.1 200 OK
 Vary: Origin
 Vary: Access-Control-Request-Method
 Vary: Access-Control-Request-Headers
-
-
-
-
-
-
- +
+

5. μ•Œλ¦Ό

+
+
+

5.1 μ•Œλ¦Ό 쑰회

+
+

HTTP μš”μ²­

+
+
GET /api/notifications HTTP/1.1
 Host: issuefy.site
-
-
-
-
-

HTTP 응닡

-
-
+
+
+
+
+

HTTP 응닡

+
+
HTTP/1.1 200 OK
 Vary: Origin
 Vary: Access-Control-Request-Method
@@ -1113,126 +2820,139 @@ 

HTTP 응닡 "notificationCreatedAt" : "2024-11-23T22:30:43.9311381", "read" : false } ]

-
-
-
-
응닡 ν•„λ“œ
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription

[].userNotificationId

Number

μ•Œλ¦Ό ID

[].orgName

String

쑰직 이름

[].repositoryName

String

μ €μž₯μ†Œ 이름

[].notificationCreatedAt

String

생성 μ‹œκ°„

[].read

Boolean

읽음 μ—¬λΆ€

-
-
-
-
-

5.2 μ•Œλ¦Ό 읽음 μ—¬λΆ€ μ—…λ°μ΄νŠΈ

-
-

HTTP μš”μ²­

-
-
+
+
+
+
응닡 ν•„λ“œ
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

[].userNotificationId +

Number +

μ•Œλ¦Ό ID

+ [].orgName

String +

쑰직 이름

[].repositoryName +

String +

μ €μž₯μ†Œ 이름

[].notificationCreatedAt +

String +

생성 μ‹œκ°„

[].read +

Boolean +

읽음 μ—¬λΆ€

+
+
+
+
+

5.2 μ•Œλ¦Ό 읽음 μ—¬λΆ€ μ—…λ°μ΄νŠΈ

+
+

HTTP μš”μ²­

+
+
PATCH /api/notifications HTTP/1.1
 Content-Type: application/json;charset=UTF-8
 Content-Length: 29
 Host: issuefy.site
 
 {"userNotificationIds":[1,2]}
-
-
-
-
μš”μ²­ ν•„λ“œ
- ----- - - - - - - - - - - - - - - -
PathTypeDescription

userNotificationIds

Array

μ—…λ°μ΄νŠΈν•  μ•Œλ¦Ό ID λͺ©λ‘

-
-
-
-

HTTP 응닡

-
-
+
+
+
+
μš”μ²­ ν•„λ“œ
+ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

userNotificationIds +

Array +

μ—…λ°μ΄νŠΈν•  μ•Œλ¦Ό ID λͺ©λ‘

+
+
+
+
+

HTTP 응닡

+
+
HTTP/1.1 200 OK
 Vary: Origin
 Vary: Access-Control-Request-Method
 Vary: Access-Control-Request-Headers
-
-
-
-
-
-
- +
+

6. λ§ˆμ΄νŽ˜μ΄μ§€

+
+
+

6.1 μ‚¬μš©μž 정보 쑰회

+
+

HTTP μš”μ²­

+
+
GET /api/user/info HTTP/1.1
 Accept: application/json
 Host: issuefy.site
-
-
-
-
-

HTTP 응닡

-
-
+
+
+
+
+

HTTP 응닡

+
+
HTTP/1.1 200 OK
 Vary: Origin
 Vary: Access-Control-Request-Method
@@ -1241,166 +2961,182 @@ 

HTTP 응닡 Content-Length: 111 {"email":"test@gmail.com","alertStatus":false,"login":"testUser","avatar_url":"https://example.com/avatar.jpg"}

-
-
-
-
응닡 ν•„λ“œ
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription

login

String

μ‚¬μš©μžμ˜ GitHub ID

avatar_url

String

μ‚¬μš©μžμ˜ GitHub ν”„λ‘œν•„ 이미지 URL

email

String

μ‚¬μš©μžμ˜ 이메일 μ£Όμ†Œ

alertStatus

Boolean

μ•Œλ¦Ό μƒνƒœ

-
-
-
-
-

6.2 μ•Œλ¦Ό μˆ˜μ‹  μ—¬λΆ€ μ—…λ°μ΄νŠΈ

-
-

HTTP μš”μ²­

-
-
+
+
+
+
응닡 ν•„λ“œ
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

login +

String +

μ‚¬μš©μžμ˜ GitHub ID

+

+ avatar_url

String +

μ‚¬μš©μžμ˜ GitHub ν”„λ‘œν•„ 이미지 + URL

email +

String +

μ‚¬μš©μžμ˜ 이메일 μ£Όμ†Œ

+ alertStatus

Boolean +

μ•Œλ¦Ό μƒνƒœ

+
+
+
+
+

6.2 μ•Œλ¦Ό μˆ˜μ‹  μ—¬λΆ€ μ—…λ°μ΄νŠΈ

+
+

HTTP μš”μ²­

+
+
PATCH /api/user/alert HTTP/1.1
 Content-Type: application/json;charset=UTF-8
 Content-Length: 20
 Host: issuefy.site
 
 {"alertStatus":true}
-
-
-
-
μš”μ²­ ν•„λ“œ
- ----- - - - - - - - - - - - - - - -
PathTypeDescription

alertStatus

Boolean

μ•Œλ¦Ό λ°›κΈ° / μ•ˆλ°›κΈ°

-
-
-
-

HTTP 응닡

-
-
+
+
+
+
μš”μ²­ ν•„λ“œ
+ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

+ alertStatus

Boolean +

μ•Œλ¦Ό λ°›κΈ° / μ•ˆλ°›κΈ°

+
+
+
+

HTTP 응닡

+
+
HTTP/1.1 204 No Content
 Vary: Origin
 Vary: Access-Control-Request-Method
 Vary: Access-Control-Request-Headers
-
-
-
-
- +
+

6.3 이메일 μ—…λ°μ΄νŠΈ

+
+

HTTP μš”μ²­

+
+
PATCH /api/user/email HTTP/1.1
 Content-Type: application/json;charset=UTF-8
 Content-Length: 30
 Host: issuefy.site
 
 {"email":"newemail@gmail.com"}
-
-
-
-
μš”μ²­ ν•„λ“œ
- ----- - - - - - - - - - - - - - - -
PathTypeDescription

email

String

μƒˆλ‘œμš΄ 이메일 μ£Όμ†Œ

-
-
-
-

HTTP 응닡

-
-
+
+
+
+
μš”μ²­ ν•„λ“œ
+ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

email +

String +

μƒˆλ‘œμš΄ 이메일 μ£Όμ†Œ

+
+
+
+

HTTP 응닡

+
+
HTTP/1.1 204 No Content
 Vary: Origin
 Vary: Access-Control-Request-Method
 Vary: Access-Control-Request-Headers
-
-
-
-
-
-
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/test/java/site/iris/issuefy/controller/SseControllerTest.java b/src/test/java/site/iris/issuefy/controller/SseControllerTest.java index 7a9adf4d..9d9c4072 100644 --- a/src/test/java/site/iris/issuefy/controller/SseControllerTest.java +++ b/src/test/java/site/iris/issuefy/controller/SseControllerTest.java @@ -3,11 +3,13 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; import static site.iris.issuefy.ApiDocumentUtils.*; import java.util.Arrays; diff --git a/terraform-bootstrap/.terraform.lock.hcl b/terraform-bootstrap/.terraform.lock.hcl new file mode 100644 index 00000000..2c4a6972 --- /dev/null +++ b/terraform-bootstrap/.terraform.lock.hcl @@ -0,0 +1,26 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.54.1" + constraints = "5.54.1" + hashes = [ + "h1:+aq386lQCaPX7wR6EPf3PPZvCiI6dRwnjb1wR6lNa8E=", + "h1:h6AA+TgBpDNQXFcLi4xKYiDbn94Dfhz7lt8Q8x8CEI8=", + "zh:37c09b9a0a0a2f7854fe52c6adb15f71593810b458a8283ed71d68036af7ba3a", + "zh:42fe11d87723d4e43b9c6224ae6bacdcb53faee8abc58f0fc625a161d1f71cb1", + "zh:57c6dfc46f28c9c2737559bd84acbc05aeae90431e731bb72a0024028a2d2412", + "zh:5ba9665a4ca0e182effd75575b19a4d47383ec02662024b9fe26f78286c36619", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:b55980be0237644123a02a30b56d4cc03863ef29036c47d6e8ab5429ab45adf5", + "zh:b81e7664f10855a3a6fc234a18b4c4f1456273126a40c41516f2061696fb9870", + "zh:bd09736ffafd92af104c3c34b5add138ae8db4402eb687863ce472ca7e5ff2e2", + "zh:cc2eb1c62fba2a11d1f239e650cc2ae94bcab01c907384dcf2e213a6ee1bd5b2", + "zh:e5dc40205d9cf6f353c0ca532ae29afc6c83928bc9bcca47d74b640d3bb5a38c", + "zh:ebf1acdcd13f10db1b9c85050ddaadc70ab269c47c5a240753362446442d8371", + "zh:f2fc28a4ad94af5e6144a7309286505e3eb7a94d9dc106722b506c372ff7f591", + "zh:f49445e8435944df122aa89853260a2716ba8b73d6a6a70cae1661554926d5a2", + "zh:fc3b5046e60ae7cab20715be23de8436eb12736136fd6d0f0cc1549ebda6cc73", + "zh:fdb98a53500e245a3b5bec077b994da6959dba8fc4eb7534528658d820e06bd5", + ] +} diff --git a/terraform-bootstrap/main.tf b/terraform-bootstrap/main.tf new file mode 100644 index 00000000..2630dda3 --- /dev/null +++ b/terraform-bootstrap/main.tf @@ -0,0 +1,80 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "5.54.1" + } + } +} + +provider "aws" { + region = "ap-northeast-2" +} + +resource "aws_s3_bucket" "issuefy_bucket" { + bucket = "issuefy-prod-terraform-state-ap-northeast-2" + force_destroy = false + + tags = { + Name = "issuefy-prod-terraform-state-ap-northeast-2" + Environment = "prod" + Owner = "2024-iris" + Project = "issuefy" + Service = "infrastructure" + } +} + +resource "aws_s3_bucket_versioning" "issuefy_bucket_versioning" { + bucket = aws_s3_bucket.issuefy_bucket.id + + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket_lifecycle_configuration" "issuefy_bucket_lifecycle" { + bucket = aws_s3_bucket.issuefy_bucket.id + + rule { + id = "state-file-lifecycle" + status = "Enabled" + + abort_incomplete_multipart_upload { + days_after_initiation = 7 + } + + noncurrent_version_expiration { + noncurrent_days = 90 + } + } +} + +resource "aws_dynamodb_table" "terraform_lock" { + name = "issuefy-terraform-lock" + billing_mode = "PAY_PER_REQUEST" + hash_key = "LockID" + + attribute { + name = "LockID" + type = "S" + } + + tags = { + Name = "issuefy-terraform-lock" + Environment = "prod" + Owner = "2024-iris" + Project = "issuefy" + Service = "infrastructure" + } +} + +module "ecr" { + source = "../terraform/modules/ecr" + + for_each = var.ecr_repositories + + repository_name = each.key + scan_on_push = each.value.scan_on_push + image_tag_mutability = each.value.image_tag_mutability + tags = each.value.tags +} \ No newline at end of file diff --git a/terraform-bootstrap/terraform.tfvars b/terraform-bootstrap/terraform.tfvars new file mode 100644 index 00000000..7fe53d32 --- /dev/null +++ b/terraform-bootstrap/terraform.tfvars @@ -0,0 +1,25 @@ +ecr_repositories = { + issuefy-was = { + scan_on_push = false + image_tag_mutability = "MUTABLE" + tags = { + Service = "was" + } + } + + issuefy-prometheus = { + scan_on_push = false + image_tag_mutability = "MUTABLE" + tags = { + Service = "prometheus" + } + } + + issuefy-web = { + scan_on_push = false + image_tag_mutability = "MUTABLE" + tags = { + Service = "web" + } + } +} diff --git a/terraform-bootstrap/variavles.tf b/terraform-bootstrap/variavles.tf new file mode 100644 index 00000000..5fe00a23 --- /dev/null +++ b/terraform-bootstrap/variavles.tf @@ -0,0 +1,7 @@ +variable "ecr_repositories" { + type = map(object({ + scan_on_push = bool + image_tag_mutability = string + tags = map(string) + })) +} diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 00000000..2c4a6972 --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,26 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.54.1" + constraints = "5.54.1" + hashes = [ + "h1:+aq386lQCaPX7wR6EPf3PPZvCiI6dRwnjb1wR6lNa8E=", + "h1:h6AA+TgBpDNQXFcLi4xKYiDbn94Dfhz7lt8Q8x8CEI8=", + "zh:37c09b9a0a0a2f7854fe52c6adb15f71593810b458a8283ed71d68036af7ba3a", + "zh:42fe11d87723d4e43b9c6224ae6bacdcb53faee8abc58f0fc625a161d1f71cb1", + "zh:57c6dfc46f28c9c2737559bd84acbc05aeae90431e731bb72a0024028a2d2412", + "zh:5ba9665a4ca0e182effd75575b19a4d47383ec02662024b9fe26f78286c36619", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:b55980be0237644123a02a30b56d4cc03863ef29036c47d6e8ab5429ab45adf5", + "zh:b81e7664f10855a3a6fc234a18b4c4f1456273126a40c41516f2061696fb9870", + "zh:bd09736ffafd92af104c3c34b5add138ae8db4402eb687863ce472ca7e5ff2e2", + "zh:cc2eb1c62fba2a11d1f239e650cc2ae94bcab01c907384dcf2e213a6ee1bd5b2", + "zh:e5dc40205d9cf6f353c0ca532ae29afc6c83928bc9bcca47d74b640d3bb5a38c", + "zh:ebf1acdcd13f10db1b9c85050ddaadc70ab269c47c5a240753362446442d8371", + "zh:f2fc28a4ad94af5e6144a7309286505e3eb7a94d9dc106722b506c372ff7f591", + "zh:f49445e8435944df122aa89853260a2716ba8b73d6a6a70cae1661554926d5a2", + "zh:fc3b5046e60ae7cab20715be23de8436eb12736136fd6d0f0cc1549ebda6cc73", + "zh:fdb98a53500e245a3b5bec077b994da6959dba8fc4eb7534528658d820e06bd5", + ] +} diff --git a/terraform/backend.tf b/terraform/backend.tf new file mode 100644 index 00000000..b16f37bd --- /dev/null +++ b/terraform/backend.tf @@ -0,0 +1,9 @@ +terraform { + backend "s3" { + bucket = "issuefy-prod-terraform-state-ap-northeast-2" + key = "prod/terraform.tfstate" + region = "ap-northeast-2" + encrypt = true + dynamodb_table = "issuefy-terraform-lock" + } +} diff --git a/terraform/locals.tf b/terraform/locals.tf new file mode 100644 index 00000000..13c416a4 --- /dev/null +++ b/terraform/locals.tf @@ -0,0 +1,314 @@ +locals { + instance_subnet_map = { + prod = module.vpc.public_subnet_ids[0] + monitoring = module.vpc.public_subnet_ids[1] + nat = module.vpc.public_subnet_ids[0] + } +} + +locals { + iam_roles = { + "ec2-to-ecs" = { + assume_role_services = ["ec2.amazonaws.com"] + policy_arns = [ + "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role", + "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess", + "arn:aws:iam::aws:policy/AmazonECS_FullAccess" + ] + tags = { + Purpose = "ECS EC2 Registration" + } + } + + "ecsTaskExecutionRole" = { + assume_role_services = ["ecs-tasks.amazonaws.com"] + policy_arns = [ + "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" + ] + tags = { + Purpose = "ECS Task Execution" + } + } + + "ec2-monitoring" = { + assume_role_services = ["ec2.amazonaws.com"] + policy_arns = [ + "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess", + "arn:aws:iam::aws:policy/AmazonS3FullAccess" + ] + tags = { + Purpose = "EC2 Instance Monitoring" + } + } + } +} + +locals { + instance_profiles = { + for name, mod in module.iam_roles : + name => mod.instance_profile_name + } + + enriched_instance_definitions = { + for name, def in var.instance_definitions : + name => merge(def, { + key_name = def.key_name, + user_data = def.user_data, + iam_instance_profile = ( + def.iam_instance_profile != null + ? ( + contains(keys(local.instance_profiles), def.iam_instance_profile) + ? local.instance_profiles[def.iam_instance_profile] + : null + ) + : null + ) + }) + } +} + + +locals { + listeners = { + web = { + port = 3000 + protocol = "HTTP" + target_group_arn = module.alb_target_group.target_group_arns["monitor"] + } + + prometheus = { + port = 9090 + protocol = "HTTP" + target_group_arn = module.alb_target_group.target_group_arns["prometheus"] + } + + loki = { + port = 3100 + protocol = "HTTP" + target_group_arn = module.alb_target_group.target_group_arns["loki"] + } + + http = { + port = 80 + protocol = "HTTP" + target_group_arn = module.alb_target_group.target_group_arns["web"] + } + + # except HTTPS listener + # https = { + # port = 443 + # protocol = "HTTPS" + # target_group_arn = module.alb_target_group.target_group_arns["web"] + # } + } +} + +locals { + target_groups = { + "web" = { + name = "web" + port = 80 + protocol = "HTTP" + path = "/" + health_check = { + path = "/" + interval = 30 + timeout = 5 + healthy_threshold = 3 + unhealthy_threshold = 2 + } + } + + "monitor" = { + name = "monitor" + port = 3000 + protocol = "HTTP" + path = "/metrics" + health_check = { + path = "/login" + } + } + + "prometheus" = { + name = "prometheus" + port = 9090 + protocol = "HTTP" + path = "/" + health_check = { + path = "/graph" + } + } + + "loki" = { + name = "loki" + port = 3100 + protocol = "HTTP" + path = "/" + health_check = { + path = "/ready" + } + } + + "was" = { + name = "was" + port = 8080 + protocol = "HTTP" + path = "/" + health_check = { + path = "/api/health" + interval = 30 + timeout = 5 + healthy_threshold = 3 + unhealthy_threshold = 2 + } + } + } +} +locals { + ecr_repo_urls = { + issuefy-was = data.aws_ecr_repository.issuefy_was.repository_url + issuefy-web = data.aws_ecr_repository.issuefy_web.repository_url + issuefy-prometheus = data.aws_ecr_repository.issuefy_prometheus.repository_url + } +} + +locals { + ecs_services = { + "issuefy-was" = { + name = "issuefy-was" + task_definition = module.ecs_task.task_definition_arns["issuefy-was"] + desired_count = 1 + iam_role_arn = module.iam_roles["ecsTaskExecutionRole"].role_arn + load_balancer = { + target_group_arn = module.alb_target_group.target_group_arns["was"] + container_name = "issuefy-was" + container_port = 8080 + } + } + + "issuefy-web" = { + name = "issuefy-web" + task_definition = module.ecs_task.task_definition_arns["issuefy-web"] + desired_count = 1 + iam_role_arn = module.iam_roles["ecsTaskExecutionRole"].role_arn + load_balancer = { + target_group_arn = module.alb_target_group.target_group_arns["web"] + container_name = "issuefy-web" + container_port = 80 + } + } + + "issuefy-promtail" = { + name = "issuefy-promtail" + task_definition = module.ecs_task.task_definition_arns["issuefy-promtail"] + desired_count = 1 + iam_role_arn = module.iam_roles["ecsTaskExecutionRole"].role_arn + } + + "issuefy-prometheus-lower" = { + name = "issuefy-prometheus-lower" + task_definition = module.ecs_task.task_definition_arns["issuefy-prometheus-lower"] + desired_count = 1 + iam_role_arn = module.iam_roles["ecsTaskExecutionRole"].role_arn + } + + "issuefy-node-exporter" = { + name = "issuefy-node-exporter" + task_definition = module.ecs_task.task_definition_arns["issuefy-node-exporter"] + desired_count = 1 + iam_role_arn = module.iam_roles["ecsTaskExecutionRole"].role_arn + } + } +} + +locals { + ecs_task_definitions = { + issuefy-was = { + cpu = 512 + memory = 717 + network_mode = "bridge" + container_image = "${local.ecr_repo_urls["issuefy-was"]}:latest" + container_port = [8080, 9136] + host_port = [0, 9136] + log_group = "/ecs/issuefy-was" + task_role_arn = module.iam_roles["ecsTaskExecutionRole"].role_arn + execution_role_arn = module.iam_roles["ecsTaskExecutionRole"].role_arn + environment = {} + volumes = [ + { + name = "issuefy-log-volume" + host_path = "/home/ec2-user/logs/" + } + ] + } + + issuefy-web = { + cpu = 256 + memory = 307 + network_mode = "bridge" + container_image = "${local.ecr_repo_urls["issuefy-web"]}:latest" + container_port = [80] + host_port = [0] + log_group = "/ecs/issuefy-web" + task_role_arn = module.iam_roles["ecsTaskExecutionRole"].role_arn + execution_role_arn = module.iam_roles["ecsTaskExecutionRole"].role_arn + environment = {} + volumes = [] + } + + issuefy-promtail = { + cpu = 256 + memory = 262 + network_mode = "host" + container_image = "grafana/promtail:latest" + container_port = [] + host_port = [] + log_group = "/ecs/issuefy-promtail" + task_role_arn = module.iam_roles["ecsTaskExecutionRole"].role_arn + execution_role_arn = module.iam_roles["ecsTaskExecutionRole"].role_arn + ## have to change + environment = { + LOKI_URL = "http://10.0.15.90:3100/loki/api/v1/push" + } + volumes = [ + { + name = "issuefy-promtail-config" + host_path = "/home/ec2-user/logs/config.yml" + }, + { + name = "issuefy-promtail-logs" + host_path = "/home/ec2-user/logs" + } + ] + } + + issuefy-prometheus-lower = { + cpu = 256 + memory = 262 + network_mode = "host" + container_image = "${local.ecr_repo_urls["issuefy-prometheus"]}:lower_1.0" + + container_port = [9090] + host_port = [9090] + log_group = "/ecs/issuefy-prometheus-lower" + task_role_arn = module.iam_roles["ecsTaskExecutionRole"].role_arn + execution_role_arn = module.iam_roles["ecsTaskExecutionRole"].role_arn + environment = {} + volumes = [] + } + + issuefy-node-exporter = { + cpu = 256 + memory = 256 + network_mode = "host" + container_image = "${local.ecr_repo_urls["issuefy-prometheus"]}:node_exporter_1.0" + container_port = [9100] + host_port = [9100] + log_group = "/ecs/issuefy-node-exporter" + task_role_arn = module.iam_roles["ecsTaskExecutionRole"].role_arn + execution_role_arn = module.iam_roles["ecsTaskExecutionRole"].role_arn + environment = {} + volumes = [] + } + } +} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 00000000..d92942ec --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,137 @@ +module "vpc" { + source = "./modules/vpc" + name_prefix = var.name_prefix + + tags = { + Environment = "prod" + } +} + +module "ec2" { + source = "./modules/ec2" + instance_subnet_map = local.instance_subnet_map + ec2_sg_id = module.security_group.ec2_sg_id + name_prefix = var.name_prefix + instance_definitions = local.enriched_instance_definitions +} + +module "security_group" { + source = "./modules/security" + name_prefix = var.name_prefix + vpc_id = module.vpc.vpc_id + + tags = { + Environment = "prod" + } +} + +data "aws_ssm_parameter" "rds_user_name" { + name = "/rds-user-name" + with_decryption = true +} + +data "aws_ssm_parameter" "rds_password" { + name = "/rds-password" + with_decryption = true +} + +module "rds" { + source = "./modules/rds" + identifier = "issuefy-db" + instance_class = "db.t3.micro" + allocated_storage = 20 + username = data.aws_ssm_parameter.rds_user_name.value + password = data.aws_ssm_parameter.rds_password.value + private_subnet_ids = module.vpc.private_subnet_ids + vpc_security_group_ids = [module.security_group.rds_sg_id] + multi_az = false + backup_retention_period = 0 + tags = { + Environment = "prod" + Service = "issuefy" + } +} + +module "iam" { + source = "./modules/iam" + group_name = "issuefy_power" + user_name = "roy_power" + + policy_arns = [ + "arn:aws:iam::aws:policy/AdministratorAccess", + "arn:aws:iam::aws:policy/AmazonElasticContainerRegistryPublicPowerUser", + "arn:aws:iam::aws:policy/AmazonS3FullAccess", + "arn:aws:iam::aws:policy/ElasticLoadBalancingReadOnly" + ] + + enable_console_access = true + enable_mfa_enforcement = true + + tags = { + Department = "issuefy" + Role = "power" + } +} + +module "iam_roles" { + source = "./modules/iamrole" + for_each = local.iam_roles + + name = each.key + assume_role_services = each.value.assume_role_services + policy_arns = each.value.policy_arns + tags = each.value.tags +} + +module "alb" { + source = "./modules/alb" + alb_security_group = module.security_group.alb_sg_id + name_prefix = var.name_prefix + subnets = module.vpc.public_subnet_ids +} + +module "alb_listener" { + source = "./modules/alb/listener" + alb_arn = module.alb.alb_arn + listeners = local.listeners +} + +module "alb_target_group" { + source = "./modules/alb/targetgroup" + target_groups = local.target_groups + vpc_id = module.vpc.vpc_id +} + +module "cloud_map" { + source = "./modules/cloudmap" + vpc_id = module.vpc.vpc_id +} + +module "ecs_cluster" { + source = "./modules/ecs/cluster" + namespace_id = module.cloud_map.namespace_id + cluster_name = "${var.name_prefix}-cluster" +} + +module "ecs_service" { + source = "./modules/ecs/service" + cluster_id = module.ecs_cluster.cluster_id + ecs_services = local.ecs_services +} + +module "ecs_task" { + source = "./modules/ecs/task" + ecs_task_definitions = local.ecs_task_definitions +} + +data "aws_ecr_repository" "issuefy_was" { + name = "issuefy-was" +} + +data "aws_ecr_repository" "issuefy_prometheus" { + name = "issuefy-prometheus" +} + +data "aws_ecr_repository" "issuefy_web" { + name = "issuefy-web" +} diff --git a/terraform/modules/alb/listener/main.tf b/terraform/modules/alb/listener/main.tf new file mode 100644 index 00000000..fc53ebbe --- /dev/null +++ b/terraform/modules/alb/listener/main.tf @@ -0,0 +1,12 @@ +resource "aws_alb_listener" "this" { + for_each = var.listeners + + load_balancer_arn = var.alb_arn + port = each.value.port + protocol = each.value.protocol + + default_action { + type = "forward" + target_group_arn = each.value.target_group_arn + } +} diff --git a/terraform/modules/alb/listener/outputs.tf b/terraform/modules/alb/listener/outputs.tf new file mode 100644 index 00000000..e69de29b diff --git a/terraform/modules/alb/listener/variables.tf b/terraform/modules/alb/listener/variables.tf new file mode 100644 index 00000000..b5a8fce8 --- /dev/null +++ b/terraform/modules/alb/listener/variables.tf @@ -0,0 +1,9 @@ +variable "alb_arn" {} + +variable "listeners" { + type = map(object({ + port = number + protocol = string + target_group_arn = string + })) +} diff --git a/terraform/modules/alb/main.tf b/terraform/modules/alb/main.tf new file mode 100644 index 00000000..7e6f0033 --- /dev/null +++ b/terraform/modules/alb/main.tf @@ -0,0 +1,13 @@ +resource "aws_alb" "issuefy_alb" { + name = "${var.name_prefix}-alb" + internal = false + load_balancer_type = var.loadbalancer_type + security_groups = [var.alb_security_group] + subnets = var.subnets + + enable_deletion_protection = false + + tags = { + Environment = "issuefy-alb" + } +} \ No newline at end of file diff --git a/terraform/modules/alb/outputs.tf b/terraform/modules/alb/outputs.tf new file mode 100644 index 00000000..2d7b410e --- /dev/null +++ b/terraform/modules/alb/outputs.tf @@ -0,0 +1,3 @@ +output "alb_arn" { + value = aws_alb.issuefy_alb.arn +} \ No newline at end of file diff --git a/terraform/modules/alb/targetgroup/main.tf b/terraform/modules/alb/targetgroup/main.tf new file mode 100644 index 00000000..fd6b3bb4 --- /dev/null +++ b/terraform/modules/alb/targetgroup/main.tf @@ -0,0 +1,8 @@ +resource "aws_lb_target_group" "issuefy_target_group" { + for_each = var.target_groups + + name = each.key + port = each.value.port + protocol = each.value.protocol + vpc_id = var.vpc_id +} diff --git a/terraform/modules/alb/targetgroup/outputs.tf b/terraform/modules/alb/targetgroup/outputs.tf new file mode 100644 index 00000000..b2fee633 --- /dev/null +++ b/terraform/modules/alb/targetgroup/outputs.tf @@ -0,0 +1,13 @@ +output "target_group_arns" { + value = { + for k, tg in aws_lb_target_group.issuefy_target_group : + k => tg.arn + } +} + +output "target_group_names" { + value = { + for k, tg in aws_lb_target_group.issuefy_target_group : + k => tg.name + } +} diff --git a/terraform/modules/alb/targetgroup/variables.tf b/terraform/modules/alb/targetgroup/variables.tf new file mode 100644 index 00000000..2bfe24d3 --- /dev/null +++ b/terraform/modules/alb/targetgroup/variables.tf @@ -0,0 +1,9 @@ +variable "target_groups" { + type = map(object({ + name = string + port = number + protocol = string + })) +} + +variable "vpc_id" {} \ No newline at end of file diff --git a/terraform/modules/alb/variables.tf b/terraform/modules/alb/variables.tf new file mode 100644 index 00000000..50c5d841 --- /dev/null +++ b/terraform/modules/alb/variables.tf @@ -0,0 +1,26 @@ +variable "name_prefix" { + type = string + description = "Prefix for ALB and related resources" +} + +variable "subnets" { + type = list(string) + description = "List of public subnet IDs for the ALB" +} + +variable "alb_security_group" { + type = string + description = "Security group ID for the ALB" +} + +variable "loadbalancer_type" { + type = string + default = "application" + description = "Type of ALB (application | network)" +} + +variable "internal" { + type = bool + default = false + description = "Whether the ALB is internal or internet-facing" +} diff --git a/terraform/modules/cloudmap/main.tf b/terraform/modules/cloudmap/main.tf new file mode 100644 index 00000000..b42213a6 --- /dev/null +++ b/terraform/modules/cloudmap/main.tf @@ -0,0 +1,4 @@ +resource "aws_service_discovery_private_dns_namespace" "issuefy-ns" { + name = "issuefy-prod" + vpc = var.vpc_id +} \ No newline at end of file diff --git a/terraform/modules/cloudmap/outputs.tf b/terraform/modules/cloudmap/outputs.tf new file mode 100644 index 00000000..cb50fe94 --- /dev/null +++ b/terraform/modules/cloudmap/outputs.tf @@ -0,0 +1,7 @@ +output "namespace_id" { + value = aws_service_discovery_private_dns_namespace.issuefy-ns.id +} + +output "namespace_arn" { + value = aws_service_discovery_private_dns_namespace.issuefy-ns.arn +} \ No newline at end of file diff --git a/terraform/modules/cloudmap/variables.tf b/terraform/modules/cloudmap/variables.tf new file mode 100644 index 00000000..b7bf843b --- /dev/null +++ b/terraform/modules/cloudmap/variables.tf @@ -0,0 +1 @@ +variable "vpc_id" {} \ No newline at end of file diff --git a/terraform/modules/ec2/main.tf b/terraform/modules/ec2/main.tf new file mode 100644 index 00000000..7307888e --- /dev/null +++ b/terraform/modules/ec2/main.tf @@ -0,0 +1,16 @@ +resource "aws_instance" "multi_role" { + for_each = var.instance_definitions + + ami = each.value.ami + instance_type = each.value.instance_type + subnet_id = var.instance_subnet_map[each.key] + vpc_security_group_ids = [var.ec2_sg_id] + iam_instance_profile = try(each.value.iam_instance_profile, null) + key_name = try(each.value.key_name, null) + user_data = try(each.value.user_data, null) + user_data_replace_on_change = true + + tags = { + Name = "${var.name_prefix}-${each.key}" + } +} \ No newline at end of file diff --git a/terraform/modules/ec2/outputs.tf b/terraform/modules/ec2/outputs.tf new file mode 100644 index 00000000..e69de29b diff --git a/terraform/modules/ec2/variables.tf b/terraform/modules/ec2/variables.tf new file mode 100644 index 00000000..6d56b22b --- /dev/null +++ b/terraform/modules/ec2/variables.tf @@ -0,0 +1,29 @@ +variable "instance_definitions" { + description = "EC2 instance definitions for different roles including AMI and instance type" + type = map(object({ + ami = string + instance_type = string + iam_instance_profile = optional(string) + key_name = string + user_data = optional(string) + })) +} + +variable "instance_subnet_map" { + type = map(string) +} + +variable "tags" { + type = map(string) + default = {} +} + +variable "name_prefix" { + description = "Name prefix for instance naming" + type = string +} + +variable "ec2_sg_id" { + description = "Security Group ID for EC2 instances" + type = string +} diff --git a/terraform/modules/ecr/main.tf b/terraform/modules/ecr/main.tf new file mode 100644 index 00000000..45d1b9f8 --- /dev/null +++ b/terraform/modules/ecr/main.tf @@ -0,0 +1,10 @@ +resource "aws_ecr_repository" "this" { + name = var.repository_name + image_tag_mutability = var.image_tag_mutability + + image_scanning_configuration { + scan_on_push = var.scan_on_push + } + + tags = var.tags +} diff --git a/terraform/modules/ecr/outputs.tf b/terraform/modules/ecr/outputs.tf new file mode 100644 index 00000000..039cf368 --- /dev/null +++ b/terraform/modules/ecr/outputs.tf @@ -0,0 +1,7 @@ +output "repository_url" { + value = aws_ecr_repository.this.repository_url +} + +output "repository_arn" { + value = aws_ecr_repository.this.arn +} diff --git a/terraform/modules/ecr/variables.tf b/terraform/modules/ecr/variables.tf new file mode 100644 index 00000000..8bf576a2 --- /dev/null +++ b/terraform/modules/ecr/variables.tf @@ -0,0 +1,15 @@ +variable "repository_name" { + type = string +} + +variable "scan_on_push" { + type = bool +} + +variable "image_tag_mutability" { + type = string +} + +variable "tags" { + type = map(string) +} diff --git a/terraform/modules/ecs/cluster/main.tf b/terraform/modules/ecs/cluster/main.tf new file mode 100644 index 00000000..68b7eb1a --- /dev/null +++ b/terraform/modules/ecs/cluster/main.tf @@ -0,0 +1,12 @@ +resource "aws_ecs_cluster" "issuefy_cluster" { + name = var.cluster_name + + setting { + name = "containerInsights" + value = "disabled" + } + + tags = { + Name = var.cluster_name + } +} \ No newline at end of file diff --git a/terraform/modules/ecs/cluster/outputs.tf b/terraform/modules/ecs/cluster/outputs.tf new file mode 100644 index 00000000..9010ec82 --- /dev/null +++ b/terraform/modules/ecs/cluster/outputs.tf @@ -0,0 +1,7 @@ +output "cluster_id" { + value = aws_ecs_cluster.issuefy_cluster.id +} + +output "cluster_name" { + value = aws_ecs_cluster.issuefy_cluster.name +} \ No newline at end of file diff --git a/terraform/modules/ecs/cluster/variables.tf b/terraform/modules/ecs/cluster/variables.tf new file mode 100644 index 00000000..9238ad35 --- /dev/null +++ b/terraform/modules/ecs/cluster/variables.tf @@ -0,0 +1,9 @@ +variable "cluster_name" { + description = "production cluster name" + default = "issuefy-cluster" +} + +variable "namespace_id" { + description = "production namespace id" + type = string +} \ No newline at end of file diff --git a/terraform/modules/ecs/service/main.tf b/terraform/modules/ecs/service/main.tf new file mode 100644 index 00000000..f9310971 --- /dev/null +++ b/terraform/modules/ecs/service/main.tf @@ -0,0 +1,9 @@ +resource "aws_ecs_service" "services" { + for_each = var.ecs_services + + name = each.key + cluster = var.cluster_id + task_definition = each.value.task_definition + desired_count = each.value.desired_count + # iam_role = each.value.iam_role_arn +} diff --git a/terraform/modules/ecs/service/outputs.tf b/terraform/modules/ecs/service/outputs.tf new file mode 100644 index 00000000..e69de29b diff --git a/terraform/modules/ecs/service/variables.tf b/terraform/modules/ecs/service/variables.tf new file mode 100644 index 00000000..c1f66ea3 --- /dev/null +++ b/terraform/modules/ecs/service/variables.tf @@ -0,0 +1,11 @@ +variable "ecs_services" { + type = map(object({ + name = string + task_definition = string + desired_count = number + })) +} + +variable "cluster_id" { + type = string +} diff --git a/terraform/modules/ecs/task/main.tf b/terraform/modules/ecs/task/main.tf new file mode 100644 index 00000000..1214bcb1 --- /dev/null +++ b/terraform/modules/ecs/task/main.tf @@ -0,0 +1,55 @@ +resource "aws_ecs_task_definition" "tasks" { + for_each = var.ecs_task_definitions + + family = each.key + network_mode = each.value.network_mode + requires_compatibilities = ["EC2"] + cpu = each.value.cpu + memory = each.value.memory + task_role_arn = each.value.task_role_arn + execution_role_arn = each.value.execution_role_arn + + dynamic "volume" { + for_each = each.value.volumes + content { + name = volume.value.name + host_path = volume.value.host_path + } + } + + container_definitions = jsonencode([ + { + name = each.key + image = each.value.container_image + cpu = each.value.cpu + memory = each.value.memory + essential = true + stopTimeout = 30 + portMappings = [ + for idx, port in each.value.container_port : { + name = "${each.key}-${port}-tcp" + containerPort = port + hostPort = each.value.host_port[idx] + protocol = "tcp" + } + ] + environment = [for k, v in each.value.environment : { name = k, value = v }] + logConfiguration = { + logDriver = "awslogs" + options = { + awslogs-group = each.value.log_group + awslogs-create-group = "true" + awslogs-region = "ap-northeast-2" + awslogs-stream-prefix = "ecs" + } + } + mountPoints = [ + for vol in each.value.volumes : { + sourceVolume = vol.name + containerPath = (vol.name == "issuefy-promtail-config" ? "/etc/promtail/config.yml" : "/logs") + readOnly = false + } + ] + } + ]) +} diff --git a/terraform/modules/ecs/task/outputs.tf b/terraform/modules/ecs/task/outputs.tf new file mode 100644 index 00000000..0f7f9214 --- /dev/null +++ b/terraform/modules/ecs/task/outputs.tf @@ -0,0 +1,6 @@ +output "task_definition_arns" { + value = { + for k, task in aws_ecs_task_definition.tasks : + k => task.arn + } +} diff --git a/terraform/modules/ecs/task/variables.tf b/terraform/modules/ecs/task/variables.tf new file mode 100644 index 00000000..95b3f058 --- /dev/null +++ b/terraform/modules/ecs/task/variables.tf @@ -0,0 +1,18 @@ +variable "ecs_task_definitions" { + type = map(object({ + container_image = string + container_port = optional(list(number), []) + host_port = optional(list(number), []) + cpu = number + memory = number + task_role_arn = string + execution_role_arn = string + network_mode = string + log_group = string + volumes = optional(list(object({ + name = string + host_path = string + })), []) + environment = optional(map(string), {}) + })) +} diff --git a/terraform/modules/iam/main.tf b/terraform/modules/iam/main.tf new file mode 100644 index 00000000..31adaf08 --- /dev/null +++ b/terraform/modules/iam/main.tf @@ -0,0 +1,47 @@ +resource "aws_iam_group" "this" { + name = var.group_name +} + +resource "aws_iam_user" "this" { + name = var.user_name + tags = var.tags +} + +resource "aws_iam_group_membership" "membership" { + name = "${var.group_name}-membership" + users = [aws_iam_user.this.name] + group = aws_iam_group.this.name +} + +resource "aws_iam_group_policy_attachment" "policies" { + for_each = toset(var.policy_arns) + + group = aws_iam_group.this.name + policy_arn = each.value +} + +resource "aws_iam_policy" "deny_if_no_mfa" { + count = var.enable_mfa_enforcement ? 1 : 0 + name = "${var.group_name}-deny-no-mfa" + policy = data.aws_iam_policy_document.deny_without_mfa.json +} + +resource "aws_iam_group_policy_attachment" "deny_no_mfa_attach" { + count = var.enable_mfa_enforcement ? 1 : 0 + group = aws_iam_group.this.name + policy_arn = aws_iam_policy.deny_if_no_mfa[0].arn +} + +data "aws_iam_policy_document" "deny_without_mfa" { + statement { + effect = "Deny" + actions = ["*"] + resources = ["*"] + + condition { + test = "BoolIfExists" + variable = "aws:MultiFactorAuthPresent" + values = ["false"] + } + } +} diff --git a/terraform/modules/iam/outputs.tf b/terraform/modules/iam/outputs.tf new file mode 100644 index 00000000..2f85767d --- /dev/null +++ b/terraform/modules/iam/outputs.tf @@ -0,0 +1,7 @@ +output "user_name" { + value = aws_iam_user.this.name +} + +output "group_name" { + value = aws_iam_group.this.name +} diff --git a/terraform/modules/iam/variables.tf b/terraform/modules/iam/variables.tf new file mode 100644 index 00000000..80785356 --- /dev/null +++ b/terraform/modules/iam/variables.tf @@ -0,0 +1,39 @@ +variable "group_name" { + description = "IAM group name" + type = string +} + +variable "user_name" { + description = "IAM user name" + type = string +} + +variable "policy_arns" { + description = "List of IAM Policy ARNs to attach to the group" + type = list(string) +} + +variable "enable_console_access" { + description = "Whether to enable AWS Management Console access" + type = bool + default = false +} + +variable "console_password" { + description = "Initial password for console login" + type = string + default = null +} + +variable "enable_mfa_enforcement" { + description = "Whether to enforce MFA via IAM policy" + type = bool + default = true +} + + +variable "tags" { + description = "Tags for the IAM user" + type = map(string) + default = {} +} diff --git a/terraform/modules/iamrole/main.tf b/terraform/modules/iamrole/main.tf new file mode 100644 index 00000000..31f9f8f6 --- /dev/null +++ b/terraform/modules/iamrole/main.tf @@ -0,0 +1,27 @@ +resource "aws_iam_role" "this" { + name = var.name + assume_role_policy = data.aws_iam_policy_document.assume_role.json + tags = var.tags +} + +resource "aws_iam_instance_profile" "this" { + name = var.name + role = aws_iam_role.this.name +} + +resource "aws_iam_role_policy_attachment" "this" { + for_each = toset(var.policy_arns) + role = aws_iam_role.this.name + policy_arn = each.value +} + +data "aws_iam_policy_document" "assume_role" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = var.assume_role_services + } + } +} diff --git a/terraform/modules/iamrole/outputs.tf b/terraform/modules/iamrole/outputs.tf new file mode 100644 index 00000000..c42acae5 --- /dev/null +++ b/terraform/modules/iamrole/outputs.tf @@ -0,0 +1,11 @@ +output "role_name" { + value = aws_iam_role.this.name +} + +output "role_arn" { + value = aws_iam_role.this.arn +} + +output "instance_profile_name" { + value = aws_iam_instance_profile.this.name +} diff --git a/terraform/modules/iamrole/variables.tf b/terraform/modules/iamrole/variables.tf new file mode 100644 index 00000000..42bbf95c --- /dev/null +++ b/terraform/modules/iamrole/variables.tf @@ -0,0 +1,21 @@ +variable "name" { + description = "IAM Role name" + type = string +} + +variable "assume_role_services" { + description = "List of services that can assume this role" + type = list(string) +} + +variable "policy_arns" { + description = "List of policy ARNs to attach to the role" + type = list(string) + default = [] +} + +variable "tags" { + description = "Tags to apply to the role" + type = map(string) + default = {} +} diff --git a/terraform/modules/rds/main.tf b/terraform/modules/rds/main.tf new file mode 100644 index 00000000..6d3a1cee --- /dev/null +++ b/terraform/modules/rds/main.tf @@ -0,0 +1,26 @@ +resource "aws_db_subnet_group" "this" { + name = "${var.identifier}-subnet-group" + subnet_ids = var.private_subnet_ids + + tags = merge(var.tags, { + Name = "${var.identifier}-subnet-group" + }) +} + +resource "aws_db_instance" "this" { + identifier = var.identifier + engine = var.engine + engine_version = var.engine_version + instance_class = var.instance_class + allocated_storage = var.allocated_storage + username = var.username + password = var.password + vpc_security_group_ids = var.vpc_security_group_ids + db_subnet_group_name = aws_db_subnet_group.this.name + multi_az = var.multi_az + backup_retention_period = var.backup_retention_period + storage_encrypted = var.storage_encrypted + skip_final_snapshot = true + + tags = var.tags +} diff --git a/terraform/modules/rds/outputs.tf b/terraform/modules/rds/outputs.tf new file mode 100644 index 00000000..b9cb73a9 --- /dev/null +++ b/terraform/modules/rds/outputs.tf @@ -0,0 +1,9 @@ +output "endpoint" { + description = "RDS endpoint address" + value = aws_db_instance.this.endpoint +} + +output "arn" { + description = "RDS instance ARN" + value = aws_db_instance.this.arn +} \ No newline at end of file diff --git a/terraform/modules/rds/variables.tf b/terraform/modules/rds/variables.tf new file mode 100644 index 00000000..7a9bd811 --- /dev/null +++ b/terraform/modules/rds/variables.tf @@ -0,0 +1,72 @@ +variable "identifier" { + description = "RDS instance identifier" + type = string +} + +variable "engine" { + description = "Database engine" + type = string + default = "mysql" +} + +variable "engine_version" { + description = "Database engine version" + type = string + default = "8.0.40" +} + +variable "instance_class" { + description = "Instance type" + type = string +} + +variable "allocated_storage" { + description = "Storage size (GB)" + type = number +} + +variable "username" { + description = "Master username" + type = string +} + +variable "password" { + description = "Master password" + type = string + sensitive = true +} + +variable "vpc_security_group_ids" { + description = "List of VPC security group IDs" + type = list(string) +} + +variable "multi_az" { + description = "Multi-AZ deployment" + type = bool + default = false +} + +variable "backup_retention_period" { + description = "Backup retention (in days)" + type = number + default = 0 +} + +variable "storage_encrypted" { + description = "Whether to encrypt storage" + type = bool + default = true +} + +variable "tags" { + description = "Tags for the RDS instance" + type = map(string) + default = {} +} + +variable "private_subnet_ids" { + description = "Private subnet IDs to associate with DB subnet group" + type = list(string) +} + diff --git a/terraform/modules/security/main.tf b/terraform/modules/security/main.tf new file mode 100644 index 00000000..4e7ef820 --- /dev/null +++ b/terraform/modules/security/main.tf @@ -0,0 +1,117 @@ +resource "aws_security_group" "ec2" { + name = "${var.name_prefix}-ec2-sg" + description = "Security group for EC2 instances" + vpc_id = var.vpc_id + + ingress { + description = "Allow SSH access" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = var.allowed_ssh_cidrs + } + + ingress { + description = "Allow HTTP access" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = var.allowed_http_cidrs + } + + egress { + description = "Allow all outbound traffic" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = merge(var.tags, { + Name = "${var.name_prefix}-ec2-sg" + }) +} + +resource "aws_security_group" "rds" { + name = "${var.name_prefix}-rds-sg" + description = "Security group for RDS instances" + vpc_id = var.vpc_id + + ingress { + description = "Allow RDS access" + from_port = 3306 + to_port = 3306 + protocol = "tcp" + cidr_blocks = var.allowed_http_cidrs + } + + egress { + description = "Allow all outbound traffic" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = merge(var.tags, { + Name = "${var.name_prefix}-rds-sg" + }) +} + +resource "aws_security_group" "alb" { + name = "${var.name_prefix}-alb-sg" + description = "Security group for Application Loadbalancer" + vpc_id = var.vpc_id + + ingress { + description = "Allow HTTPS access" + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = var.allowed_ssh_cidrs + } + + ingress { + description = "Allow HTTP access" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = var.allowed_http_cidrs + } + + ingress { + description = "Allow NginX access" + from_port = 3000 + to_port = 3000 + protocol = "tcp" + cidr_blocks = var.allowed_http_cidrs + } + + ingress { + description = "Allow Loki access" + from_port = 3001 + to_port = 3001 + protocol = "tcp" + cidr_blocks = var.allowed_http_cidrs + } + + ingress { + description = "Allow Prometheus access" + from_port = 9090 + to_port = 9090 + protocol = "tcp" + cidr_blocks = var.allowed_http_cidrs + } + + egress { + description = "Allow all outbound traffic" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = merge(var.tags, { + Name = "${var.name_prefix}-alb-sg" + }) +} diff --git a/terraform/modules/security/outputs.tf b/terraform/modules/security/outputs.tf new file mode 100644 index 00000000..7d6e8b19 --- /dev/null +++ b/terraform/modules/security/outputs.tf @@ -0,0 +1,14 @@ +output "ec2_sg_id" { + description = "Security group ID for EC2" + value = aws_security_group.ec2.id +} + +output "rds_sg_id" { + description = "Security group ID for RDS" + value = aws_security_group.rds.id +} + +output "alb_sg_id" { + description = "Security group ID for ALB" + value = aws_security_group.alb.id +} \ No newline at end of file diff --git a/terraform/modules/security/variables.tf b/terraform/modules/security/variables.tf new file mode 100644 index 00000000..5bbeb8b0 --- /dev/null +++ b/terraform/modules/security/variables.tf @@ -0,0 +1,27 @@ +variable "name_prefix" { + description = "Prefix for naming the security group" + type = string +} + +variable "vpc_id" { + description = "VPC ID where the security group will be created" + type = string +} + +variable "allowed_ssh_cidrs" { + description = "CIDR blocks allowed for SSH access" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "allowed_http_cidrs" { + description = "CIDR blocks allowed for HTTP access" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "tags" { + description = "Tags to apply to the security group" + type = map(string) + default = {} +} diff --git a/terraform/modules/vpc/main.tf b/terraform/modules/vpc/main.tf new file mode 100644 index 00000000..032d74ff --- /dev/null +++ b/terraform/modules/vpc/main.tf @@ -0,0 +1,76 @@ +resource "aws_vpc" "this" { + cidr_block = var.vpc_cidr + enable_dns_hostnames = var.enable_dns_hostnames + enable_dns_support = var.enable_dns_support + enable_network_address_usage_metrics = var.enable_network_address_usage_metrics + + tags = merge(var.tags, { + Name = "${var.name_prefix}-vpc" + }) +} + +resource "aws_subnet" "public" { + count = length(var.public_subnet_cidrs) + vpc_id = aws_vpc.this.id + cidr_block = var.public_subnet_cidrs[count.index] + availability_zone = var.availability_zones[count.index] + map_public_ip_on_launch = true + + tags = merge(var.tags, { + Name = "${var.name_prefix}-pub-sub-${substr(var.availability_zones[count.index], -2, 2)}" + }) +} + +resource "aws_subnet" "private" { + count = length(var.private_subnet_cidrs) + vpc_id = aws_vpc.this.id + cidr_block = var.private_subnet_cidrs[count.index] + availability_zone = var.availability_zones[count.index] + + tags = merge(var.tags, { + Name = "${var.name_prefix}-pri-sub-${substr(var.availability_zones[count.index], -2, 2)}" + }) +} + +resource "aws_internet_gateway" "this" { + vpc_id = aws_vpc.this.id + + tags = merge(var.tags, { + Name = "${var.name_prefix}-igw" + }) +} + +resource "aws_route_table" "public" { + vpc_id = aws_vpc.this.id + + tags = merge(var.tags, { + Name = "${var.name_prefix}-rt-pub" + }) +} + +resource "aws_route_table" "private" { + count = length(var.private_subnet_cidrs) + vpc_id = aws_vpc.this.id + + tags = merge(var.tags, { + Name = "${var.name_prefix}-rt-pri-${substr(var.availability_zones[count.index], -2, 2)}" + }) +} + +resource "aws_route_table_association" "public" { + count = length(var.public_subnet_cidrs) + subnet_id = aws_subnet.public[count.index].id + route_table_id = aws_route_table.public.id +} + +resource "aws_route_table_association" "private" { + count = length(var.private_subnet_cidrs) + subnet_id = aws_subnet.private[count.index].id + route_table_id = aws_route_table.private[count.index].id +} + +resource "aws_route" "public_internet_gateway" { + route_table_id = aws_route_table.public.id + destination_cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.this.id +} \ No newline at end of file diff --git a/terraform/modules/vpc/outputs.tf b/terraform/modules/vpc/outputs.tf new file mode 100644 index 00000000..7bf9121d --- /dev/null +++ b/terraform/modules/vpc/outputs.tf @@ -0,0 +1,34 @@ +output "vpc_id" { + description = "The ID of the VPC" + value = aws_vpc.this.id +} + +output "vpc_cidr_block" { + description = "The CIDR block of the VPC" + value = aws_vpc.this.cidr_block +} + +output "public_subnet_ids" { + description = "List of IDs of public subnets" + value = aws_subnet.public[*].id +} + +output "private_subnet_ids" { + description = "List of IDs of private subnets" + value = aws_subnet.private[*].id +} + +output "internet_gateway_id" { + description = "ID of the Internet Gateway" + value = aws_internet_gateway.this.id +} + +output "public_route_table_id" { + description = "ID of the public route table" + value = aws_route_table.public.id +} + +output "private_route_table_ids" { + description = "List of IDs of private route tables" + value = aws_route_table.private[*].id +} \ No newline at end of file diff --git a/terraform/modules/vpc/variables.tf b/terraform/modules/vpc/variables.tf new file mode 100644 index 00000000..0b30ee54 --- /dev/null +++ b/terraform/modules/vpc/variables.tf @@ -0,0 +1,53 @@ +variable "vpc_cidr" { + description = "CIDR block for VPC" + type = string + default = "10.0.0.0/16" +} + +variable "enable_dns_hostnames" { + description = "Enable DNS hostnames in VPC" + type = bool + default = true +} + +variable "enable_dns_support" { + description = "Enable DNS support in VPC" + type = bool + default = true +} + +variable "enable_network_address_usage_metrics" { + description = "Enable network address usage metrics" + type = bool + default = true +} + +variable "public_subnet_cidrs" { + description = "CIDR blocks for public subnets" + type = list(string) + default = ["10.0.0.0/20", "10.0.16.0/20"] +} + +variable "private_subnet_cidrs" { + description = "CIDR blocks for private subnets" + type = list(string) + default = ["10.0.128.0/20", "10.0.144.0/20"] +} + +variable "availability_zones" { + description = "Availability zones for subnets" + type = list(string) + default = ["ap-northeast-2a", "ap-northeast-2c"] +} + +variable "tags" { + description = "Tags to apply to resources" + type = map(string) + default = {} +} + +variable "name_prefix" { + description = "Prefix to use for resource names" + type = string + default = "issuefy" +} \ No newline at end of file diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 00000000..e69de29b diff --git a/terraform/providers.tf b/terraform/providers.tf new file mode 100644 index 00000000..7b61b44e --- /dev/null +++ b/terraform/providers.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "5.54.1" + } + } +} + +provider "aws" { + region = "ap-northeast-2" +} \ No newline at end of file diff --git a/terraform/terraform.tfvars b/terraform/terraform.tfvars new file mode 100644 index 00000000..037afd79 --- /dev/null +++ b/terraform/terraform.tfvars @@ -0,0 +1,32 @@ +instance_definitions = { + prod = { + ami = "ami-012ea6058806ff688" + instance_type = "t3a.small" + iam_instance_profile = "ec2-to-ecs" + key_name = "issuefy-key" + user_data = <<-EOF +#!/bin/bash +echo ECS_CLUSTER=issuefy-cluster >> /etc/ecs/ecs.config +EOF + } + + monitoring = { + ami = "ami-05377cf8cfef186c2" + instance_type = "t2.micro" + iam_instance_profile = "ec2-monitoring" + key_name = "issuefy-key" + user_data = <<-EOF +#!/bin/bash +dnf update -y +dnf install -y docker +systemctl enable docker +systemctl start docker +EOF + } + + nat = { + ami = "ami-0fa9216d5e4fcd66d" + instance_type = "t3.nano" + key_name = "issuefy-key" + } +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 00000000..82522558 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,22 @@ +variable "name_prefix" { + description = "project name prefix" + type = string + default = "issuefy" +} + +variable "instance_definitions" { + type = map(object({ + ami = string + instance_type = string + iam_instance_profile = optional(string) + key_name = string + user_data = optional(string) + })) +} + +variable "tags" { + type = map(string) + default = {} +} + +