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

Oone #7

Open
wants to merge 14 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
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.exe filter=lfs diff=lfs merge=lfs -text
*.exe filter=lfs diff=lfs merge=lfs -text
49 changes: 49 additions & 0 deletions .github/workflows/docker-image.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Docker Image CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:

build-and-push:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read

steps:
- uses: actions/checkout@v4

# Step 1: Log in to Amazon ECR
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: arn:aws:iam::891376975226:role/OIDCRolefors3
aws-region: us-east-1

- name: Login to Amazon ECR
run: |
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 891376975226.dkr.ecr.us-east-1.amazonaws.com

# Step 2: Build the Docker image
- name: Build the Docker image
run: |
IMAGE_NAME=$(basename "$PWD")
docker build . --file Dockerfile --tag $IMAGE_NAME:latest

# Step 3: Tag the image with ECR repository URL
- name: Tag the Docker image
run: |
IMAGE_NAME=$(basename "$PWD")
ECR_URI=891376975226.dkr.ecr.us-east-1.amazonaws.com/$IMAGE_NAME
docker tag $IMAGE_NAME:latest $ECR_URI:latest

# Step 4: Push the Docker image to ECR
- name: Push the Docker image to ECR
run: |
IMAGE_NAME=$(basename "$PWD")
ECR_URI=891376975226.dkr.ecr.us-east-1.amazonaws.com/$IMAGE_NAME
docker push $ECR_URI:latest
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
docker_registry_secret:
kubectl create secret docker-registry regcred \
--docker-server={AWS-ACCOUNT}.dkr.ecr.{}.amazonaws.com \
--docker-username=AWS \
--docker-password=$(aws ecr get-login-password) \

docker-build:
docker build -f bird/Dockerfile -t bird .


docker-push:


k8s-deploy:
19 changes: 19 additions & 0 deletions bird/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM golang:1.22.5-alpine AS builder

WORKDIR /app

COPY go.mod ./
RUN go mod download

# Copy the rest of the application source code
COPY . .

RUN go build -o api .

FROM alpine:3.18

WORKDIR /app

COPY --from=builder /app/api /app/api

CMD ["./api"]
67 changes: 50 additions & 17 deletions bird/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import (
"encoding/json"
"fmt"
"io"
"math/rand/v2"
"math/rand"
"net/http"
"net/url"
"time"
)

type Bird struct {
Expand All @@ -16,6 +17,7 @@ type Bird struct {
Image string
}

// Function to return a default bird in case of errors
func defaultBird(err error) Bird {
return Bird{
Name: "Bird in disguise",
Expand All @@ -24,48 +26,79 @@ func defaultBird(err error) Bird {
}
}

// Function to get bird image by bird name
func getBirdImage(birdName string) (string, error) {
res, err := http.Get(fmt.Sprintf("http://localhost:4200?birdName=%s", url.QueryEscape(birdName)))
if err != nil {
return "", err
}
body, err := io.ReadAll(res.Body)
return string(body), err
res, err := http.Get(fmt.Sprintf("http://localhost:4200?birdName=%s", url.QueryEscape(birdName)))
if err != nil {
return "", err
}
defer res.Body.Close() // Ensure body is closed after reading

body, err := io.ReadAll(res.Body)
if err != nil {
return "", err
}
return string(body), nil
}

// Function to fetch bird factoid from API
func getBirdFactoid() Bird {
res, err := http.Get(fmt.Sprintf("%s%d", "https://freetestapi.com/api/v1/birds/", rand.IntN(50)))
res, err := http.Get(fmt.Sprintf("%s%d", "https://freetestapi.com/api/v1/birds/", rand.Intn(50)))
if err != nil {
fmt.Printf("Error reading bird API: %s\n", err)
return defaultBird(err)
}
defer res.Body.Close() // Ensure body is closed after reading

body, err := io.ReadAll(res.Body)
if err != nil {
fmt.Printf("Error parsing bird API response: %s\n", err)
return defaultBird(err)
}

var bird Bird
err = json.Unmarshal(body, &bird)
if err != nil {
fmt.Printf("Error unmarshalling bird: %s", err)
fmt.Printf("Error unmarshalling bird: %s\n", err)
return defaultBird(err)
}

// Fetch bird image
birdImage, err := getBirdImage(bird.Name)
if err != nil {
fmt.Printf("Error in getting bird image: %s\n", err)
return defaultBird(err)
}
birdImage, err := getBirdImage(bird.Name)
if err != nil {
fmt.Printf("Error in getting bird image: %s\n", err)
return defaultBird(err)
}
bird.Image = birdImage
bird.Image = birdImage
return bird
}

// HTTP handler for bird factoid endpoint
func bird(w http.ResponseWriter, r *http.Request) {
var buffer bytes.Buffer
json.NewEncoder(&buffer).Encode(getBirdFactoid())
io.WriteString(w, buffer.String())
err := json.NewEncoder(&buffer).Encode(getBirdFactoid())
if err != nil {
http.Error(w, "Failed to encode bird factoid", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(buffer.Bytes())
}

// HTTP handler for health check endpoint
func healthCheck(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
}

func main() {
// Seed the random generator
rand.Seed(time.Now().UnixNano())

// Define HTTP handlers
http.HandleFunc("/", bird)
http.HandleFunc("/healthz", healthCheck)

// Start the HTTP server
fmt.Println("Server is starting on port 4201...")
http.ListenAndServe(":4201", nil)
}
21 changes: 21 additions & 0 deletions birdImage/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM golang:1.22.5-alpine AS builder

WORKDIR /app

COPY go.mod ./
RUN go mod download

# Copy the rest of the application source code
COPY . .

RUN go build -o api .

FROM alpine:3.18

WORKDIR /app

COPY --from=builder /app/api /app/api

EXPOSE 8080

CMD ["./api"]
54 changes: 37 additions & 17 deletions birdImage/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,62 +10,82 @@ import (
)

type Urls struct {
Thumb string
Thumb string `json:"thumb"`
}

type Links struct {
Urls Urls
Urls Urls `json:"urls"`
}

type ImageResponse struct {
Results []Links
Results []Links `json:"results"`
}

type Bird struct {
Image string
}

// Default image URL in case of errors or no results
func defaultImage() string {
return "https://www.pokemonmillennium.net/wp-content/uploads/2015/11/missingno.png"
return "https://www.pokemonmillennium.net/wp-content/uploads/2015/11/missingno.png"
}

// Function to get bird image using Unsplash API
func getBirdImage(birdName string) string {
var query = fmt.Sprintf(
"https://api.unsplash.com/search/photos?page=1&query=%s&client_id=P1p3WPuRfpi7BdnG8xOrGKrRSvU1Puxc1aueUWeQVAI&per_page=1",
url.QueryEscape(birdName),
)
query := fmt.Sprintf(
"https://api.unsplash.com/search/photos?page=1&query=%s&client_id=P1p3WPuRfpi7BdnG8xOrGKrRSvU1Puxc1aueUWeQVAI&per_page=1",
url.QueryEscape(birdName),
)
res, err := http.Get(query)
if err != nil {
fmt.Printf("Error reading image API: %s\n", err)
return defaultImage()
}
defer res.Body.Close() // Ensure the body is closed after reading

body, err := io.ReadAll(res.Body)
if err != nil {
fmt.Printf("Error parsing image API response: %s\n", err)
return defaultImage()
}

var response ImageResponse
err = json.Unmarshal(body, &response)
if err != nil {
fmt.Printf("Error unmarshalling bird image: %s", err)
fmt.Printf("Error unmarshalling bird image: %s\n", err)
return defaultImage()
}

// Check if the result contains any images
if len(response.Results) == 0 {
fmt.Println("No images found for the bird")
return defaultImage()
}
return response.Results[0].Urls.Thumb

return response.Results[0].Urls.Thumb
}

// Handler for fetching bird image
func bird(w http.ResponseWriter, r *http.Request) {
var buffer bytes.Buffer
birdName := r.URL.Query().Get("birdName")
if birdName == "" {
json.NewEncoder(&buffer).Encode(defaultImage())
} else {
json.NewEncoder(&buffer).Encode(getBirdImage(birdName))
}
birdName := r.URL.Query().Get("birdName")
if birdName == "" {
json.NewEncoder(&buffer).Encode(defaultImage())
} else {
json.NewEncoder(&buffer).Encode(getBirdImage(birdName))
}
w.Header().Set("Content-Type", "application/json")
io.WriteString(w, buffer.String())
}

// Health check handler
func healthCheck(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok")) // Removed the incorrect `=`
}

func main() {
http.HandleFunc("/", bird)
http.HandleFunc("/healthz", healthCheck)
fmt.Println("Server running on port 4200...")
http.ListenAndServe(":4200", nil)
}

34 changes: 34 additions & 0 deletions infra/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
About:
This simple infra was provisioned on AWS using IAC(Terraform) to manage a k8s application already deployed using helm.

Steps to Reproduce
A list of the resources provisioned include:
1. AWS vpc - Provisioned using a module
2. AWS ec2 instance with ubuntu AMI (provisioned using a data source) - The following commands were added to the userdata as part of the bootscript in order to install k8s and docker within the instance

curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--tls-san your_server_ip" sh -
sudo apt update -y && sudo apt install docker.io -y
sudo usermod -aG docker $USER
newgrp docker

3. AWS security group
4. AWS ECR

As soon as the ec2 instance is deployed you will be able to ssh into it using
ssh -i <private-key> ubuntu@<public-ip-addess> or using ec2-instance-connect. The k3 will already be installed via the user data.
An attempt to run kubectl get nodes creates the following error:
'''k3s kubectl version
WARN[0000] Unable to read /etc/rancher/k3s/k3s.yaml, please start server with --write-kubeconfig-mode to modify kube config permissions
error: error loading config file "/etc/rancher/k3s/k3s.yaml" : open /etc/rancher/k3s/k3s.yaml: permission denied'''

in order to overcome this , run the following commands:
export KUBECONFIG=~/.kube/config
mkdir ~/.kube 2> /dev/null
sudo k3s kubectl config view --raw > "$KUBECONFIG"
chmod 600 "$KUBECONFIG"

afterwards all commands can run
its important to change the default namespace for the context to bird using the following commands :
<kubectl config set-context --current --namespace=<namespace>>
<kubectl config view | grep namespace> command to verify it has been changed to bird as default
25 changes: 25 additions & 0 deletions infra/ami.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#data source for the AMI
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["amazon"]

filter {
name = "name"
values = ["al2023-ami-*"]
}

filter {
name = "root-device-type"
values = ["ebs"]
}

filter {
name = "virtualization-type"
values = ["hvm"]
}

filter {
name = "architecture"
values = ["x86_64"]
}
}
Loading