Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Phase11 #10

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Release

on:
Copy link
Member

Choose a reason for hiding this comment

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

این اوکیه ولی چرا حالت ساده‌تر push روی main رو انتخاب نکردی؟

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

یکم دست آدم رو بازتر میذاره. مثلا الان تنظیم کردم فقط موقعی که PRمون از phase05 مرج میشه اجرا بشه چون اگه سورس تغییر نکنه ایمیج نهایی هم تکراری میشه و فقط بی دلیل ورژن میخوره.

pull_request:
branches:
- main
types:
- closed

jobs:
tag:
# If the PR is merged from phase05, then we will bump the version and push a new tag
if: ${{ github.event.pull_request.merged == true && github.head_ref == 'phase05' }}
runs-on: ubuntu-latest

permissions:
contents: write

outputs:
new_tag: ${{ steps.generate_tag.outputs.new_tag }}
Copy link
Member

Choose a reason for hiding this comment

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

این کجا استفاده می‌شه؟

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

فعلا جای خاصی استفاده نمیشه اما jobی که کارش tag زدنه به نظرم منطقی باشه تگی که زده رو هم برکردونه.

vversion: ${{ steps.generate_tag.outputs.new_tag }}
version: ${{ steps.pure_version.outputs.version }}

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Bump version and push tag
id: generate_tag
uses: anothrNick/github-tag-action@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WITH_V: true
DEFAULT_BUMP: patch
PATCH_STRING_TOKEN: fix
MINOR_STRING_TOKEN: feat
MAJOR_STRING_TOKEN: BREAKING

- name: Pure version
Copy link
Member

Choose a reason for hiding this comment

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

چه نیازی به این کار هست؟

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

منظورتون pure_version هست؟ راستش چون actionی که استفاده کردم خودش tag میزنه و push میکنه و من میخواستم تگ ها قبلشون v داشته باشن WITH_V رو روشن گذاشتم اما ایمیج های داکر رو نمیخواستم v داشته باشن برای همین با sed حرف v رو از اول tag حذف میکنم و برمیگردونم. البته در نهایت هردو تگ با و بدون v رو گذاشتم برای داکر.

id: pure_version
run: echo "version=$(echo ${{ steps.generate_tag.outputs.new_tag }} | sed 's/^v//')" | tee -a $GITHUB_ENV

release:
runs-on: ubuntu-latest
needs:
- tag

permissions:
packages: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Metadata extraction
id: metadata
uses: docker/metadata-action@v5
with:
# List of Docker images to use as base name for tags
images: |
ghcr.io/${{ github.repository }}/traceroute-api

# Generates Docker tags based on the following events/attributes
tags: |
type=raw,value=${{ needs.tag.outputs.version }}
type=raw,value=${{ needs.tag.outputs.vversion }}
latest

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v3
with:
push: true
context: "{{defaultContext}}:Phase-05"
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*
!/**/
!*.*
!Dockerfile
29 changes: 29 additions & 0 deletions Phase-05/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# <Build stage>
FROM golang:1.22.5-alpine3.20 AS build

WORKDIR /app

RUN adduser -D -g '' -u 10001 builder
RUN chown -R builder:builder /app
USER builder

COPY go.mod go.sum ./
RUN go mod download

COPY *.go ./
RUN go build -o ./traceroute-api
# </Build stage>

# <Final stage>
FROM alpine:3.20.0 AS final

WORKDIR /app

LABEL org.opencontainers.image.source=https://github.com/Star-Academy/Summer1403-Devops-Team12

COPY --from=build --chown=root:root /app/traceroute-api ./traceroute-api

EXPOSE 8080

CMD ["./traceroute-api"]
# </Final stage>
31 changes: 31 additions & 0 deletions Phase-05/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# How to run

## build
```bash
$ go build -o main
```

## Run
```bash
# Listening to ICMP packets requires root privileges
$ sudo ./main
```

## Usage
```bash
$ curl http://localhost:8080/trace/{ip}
```
```bash
$ curl http://localhost:8080/trace/{ip}?maxHops={max_hops}
```

## Example
```bash
$ curl http://localhost:8080/trace/8.8.8.8
```
```bash
$ curl http://localhost:8080/trace/google.com
```
```bash
$ curl http://localhost:8080/trace/8.8.8.8?maxHops=10
```
7 changes: 7 additions & 0 deletions Phase-05/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import (
"os"
)

var redisConnStr = defaultString(os.Getenv("REDIS_CONN_STR"), "redis://localhost:6379")
19 changes: 19 additions & 0 deletions Phase-05/dto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package main

type TraceHopResponse struct {
Hop int `json:"hop"`
IPAddr string `json:"ip"`
RTT int64 `json:"rtt"`
}

func (hop *TraceHop) toTraceHopResponse(hopIndex int) *TraceHopResponse {
if hop == nil {
return &TraceHopResponse{Hop: hopIndex + 1, IPAddr: "", RTT: -1}
} else {
return &TraceHopResponse{
Hop: hopIndex + 1,
IPAddr: hop.IPAddr.String(),
RTT: hop.RTT.Milliseconds(),
}
}
}
11 changes: 11 additions & 0 deletions Phase-05/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module phase05

go 1.22.5

require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sys v0.22.0 // indirect
)
10 changes: 10 additions & 0 deletions Phase-05/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
13 changes: 13 additions & 0 deletions Phase-05/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

import "log"

func main() {
err := initRedis()
if err != nil {
log.Fatal(err)
}
log.Println("Successfully connected to Redis")

RunTraceRouteServer(":8080")
}
62 changes: 62 additions & 0 deletions Phase-05/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"log"
"net/http"
"regexp"
"strconv"
"strings"
)

var DomainRegex = regexp.MustCompile(`^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`)
var AddrRegex = regexp.MustCompile(`^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`)

func traceRouteHandler(w http.ResponseWriter, r *http.Request) {
path, _, _ := strings.Cut(r.URL.Path, "?")
trimmedPath := strings.Trim(path, "/")
addr := trimmedPath[strings.LastIndex(trimmedPath, "/")+1:]

if !AddrRegex.MatchString(addr) && !DomainRegex.MatchString(addr) {
WriteBadRequest(w, "Invalid addr "+addr)
return
}

maxHops, err := strconv.Atoi(defaultString(r.URL.Query().Get("maxHops"), "30"))
if err != nil {
WriteBadRequest(w, "maxHops must be an integer")
return
}

hops, err := TraceRoute(addr, maxHops)
if err != nil {
WriteError(w, err.Error(), http.StatusInternalServerError)
return
}

result := make([]*TraceHopResponse, len(hops))
for i, hop := range hops {
result[i] = hop.toTraceHopResponse(i)
}

response, err := WriteJSON(w, result)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

err = saveToRedis(GenerateRedisKey(addr, maxHops), response)
if err != nil {
log.Fatal(err)
}
}

func RunTraceRouteServer(listen string) {
handler := &RegexpHandler{}
handler.HandleFunc(regexp.MustCompile(`^/trace/[^/]+$`), traceRouteHandler)

log.Printf("Listening on %s\n", listen)
err := http.ListenAndServe(listen, handler)
if err != nil {
log.Fatalf("Failed to start server. %v", err)
}
}
39 changes: 39 additions & 0 deletions Phase-05/storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

import (
"context"

"github.com/go-redis/redis/v8"
)

var (
ctx = context.Background()
rdb *redis.Client = nil
)

func initRedis() error {
if rdb == nil {
opts, err := redis.ParseURL(redisConnStr)
if err != nil {
return err
}

rdb = redis.NewClient(opts)
return rdb.Ping(ctx).Err()
}

return nil
}

func saveToRedis(key string, value []byte) error {
if rdb == nil {
initRedis()
}

err := rdb.Set(ctx, key, value, 0).Err()
if err != nil {
return err
}

return nil
}
Loading