From b2d4011c1927baecce30294fad3ed963b91c977e Mon Sep 17 00:00:00 2001 From: Chirayu Kapoor Date: Wed, 29 May 2024 19:04:19 +0530 Subject: [PATCH] Initial commit Signed-off-by: Chirayu Kapoor --- .dockerignore | 24 +++++++++++ .github/workflows/docker-image.yml | 31 ++++++++++++++ .github/workflows/go-test.yml | 27 +++++++++++++ Dockerfile | 20 +++++++++ LICENSE | 19 +++++++++ README.md | 53 ++++++++++++++++++++++++ go.mod | 3 ++ main.go | 44 ++++++++++++++++++++ stack/stack.go | 36 +++++++++++++++++ stack/stack_test.go | 65 ++++++++++++++++++++++++++++++ 10 files changed, 322 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/docker-image.yml create mode 100644 .github/workflows/go-test.yml create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 go.mod create mode 100644 main.go create mode 100644 stack/stack.go create mode 100644 stack/stack_test.go diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..02913ce --- /dev/null +++ b/.dockerignore @@ -0,0 +1,24 @@ +# Exclude the GitHub Actions workflows +.github/ + +# Exclude Git repository files +.git +.gitignore + +# Exclude Go test files +stack/stack_test.go + +# Exclude local Go build files and caches +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out + +# Exclude any local environment files +.env + +Dockerfile +README.md diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..6f4f9c8 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,31 @@ +name: Build and Push Docker Image + +on: + workflow_run: + workflows: ["Go Test"] + types: + - completed + +jobs: + build: + + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Build Docker image + run: docker build -t ${{ secrets.DOCKER_USER }}/go-stack:${{ github.sha }} . + - name: Tag for latest + run: docker tag ${{ secrets.DOCKER_USER }}/go-stack:${{ github.sha }} ${{ secrets.DOCKER_USER }}/go-stack:latest + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Push Docker images + run: docker push --all-tags ${{ secrets.DOCKER_USER }}/go-stack \ No newline at end of file diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml new file mode 100644 index 0000000..8e333d4 --- /dev/null +++ b/.github/workflows/go-test.yml @@ -0,0 +1,27 @@ +name: Go Test + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.22 + + - name: Install dependencies + run: go mod tidy + + - name: Run tests + run: go test -v ./... diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7e6ba47 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +# Use an official Golang runtime as a parent image +FROM golang:1.22 + +# Set the Current Working Directory inside the container +WORKDIR /app + +# Copy go.mod and go.sum files +COPY go.mod go.sum ./ + +# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed +RUN go mod download + +# Copy the local package files to the container’s workspace +COPY . . + +# Build the Go app +RUN go build -o main . + +# Run the executable +ENTRYPOINT ["./main"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9cf1062 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4037bd2 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# go-stack + +[![Build Status](https://github.com/chiukapoor/go-stack/actions/workflows/docker-image.yml/badge.svg)](https://github.com/chiukapoor/go-stack/actions) +[![License](https://img.shields.io/github/license/chiukapoor/go-stack)](https://github.com/chiukapoor/go-stack/blob/main/LICENSE) +[![Docker Repository](https://img.shields.io/badge/docker-repo-blue)](https://hub.docker.com/r/csociety/go-stack) + + +## Description + +This repository contains a simple implementation of a thread-safe stack in Go. It includes concurrent push and pop operations, unit tests, and a Dockerfile for containerization. + +## Features + +- Concurrency support +- Dockerized +- Kubernetes-ready +- CI/CD with GitHub Actions + +## Getting Started + +### Prerequisites + +- Go 1.22 or later +- Docker + +### Running the Application + +#### Locally + +```bash +go run main.go +``` + +#### Docker + +```bash +docker run csociety/go-stack +``` + +#### Kubernetes + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: go-stack + labels: + app: go-stack +spec: + containers: + - name: go-stack + image: csociety/go-stack:latest +``` \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a837083 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/chiukapoor/go-stack + +go 1.22.3 diff --git a/main.go b/main.go new file mode 100644 index 0000000..ee6d618 --- /dev/null +++ b/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + "sync" + + "github.com/chiukapoor/go-stack/stack" +) + +func main() { + stack := &stack.Stack{ + Nums: []int{}, + } + + var wg sync.WaitGroup + + // Start 50 goroutines to push items to the stack + for i := 0; i < 50; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + stack.Push(i) + fmt.Printf("Pushed: %d\n", i) + }(i) + } + + // Start 25 goroutines to pop items from the stack + for i := 0; i < 25; i++ { + wg.Add(1) + go func() { + defer wg.Done() + item, err := stack.Pop() + if err != nil { + fmt.Println("Pop error:", err) + } else { + fmt.Printf("Popped: %d\n", item) + } + }() + } + + wg.Wait() + + fmt.Println("Final Stack:", stack.Nums) +} diff --git a/stack/stack.go b/stack/stack.go new file mode 100644 index 0000000..5cf9c40 --- /dev/null +++ b/stack/stack.go @@ -0,0 +1,36 @@ +package stack + +import ( + "errors" + "sync" +) + +// Stack is represented as a struct of list, it has three functions: Pop, Push, and IsEmpty +type Stack struct { + Nums []int + lock sync.Mutex +} + +// Pop removes the top item from the stack and returns it +func (s *Stack) Pop() (int, error) { + s.lock.Lock() + defer s.lock.Unlock() + if s.IsEmpty() { + return 0, errors.New("empty stack") + } + item := s.Nums[len(s.Nums)-1] + s.Nums = s.Nums[:len(s.Nums)-1] + return item, nil +} + +// Push adds an item to the stack +func (s *Stack) Push(num int) { + s.lock.Lock() + defer s.lock.Unlock() + s.Nums = append(s.Nums, num) +} + +// IsEmpty checks if the stack is empty +func (s *Stack) IsEmpty() bool { + return len(s.Nums) == 0 +} diff --git a/stack/stack_test.go b/stack/stack_test.go new file mode 100644 index 0000000..44681a9 --- /dev/null +++ b/stack/stack_test.go @@ -0,0 +1,65 @@ +package stack + +import ( + "testing" +) + +func TestPush(t *testing.T) { + stack := &Stack{} + + stack.Push(1) + if len(stack.Nums) != 1 { + t.Errorf("expected stack size 1, got %d", len(stack.Nums)) + } + + stack.Push(2) + if len(stack.Nums) != 2 { + t.Errorf("expected stack size 2, got %d", len(stack.Nums)) + } +} + +func TestPop(t *testing.T) { + stack := &Stack{} + + stack.Push(1) + stack.Push(2) + + item, err := stack.Pop() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if item != 2 { + t.Errorf("expected 2, got %d", item) + } + + item, err = stack.Pop() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if item != 1 { + t.Errorf("expected 1, got %d", item) + } + + _, err = stack.Pop() + if err == nil { + t.Errorf("expected error, got nil") + } +} + +func TestIsEmpty(t *testing.T) { + stack := &Stack{} + + if !stack.IsEmpty() { + t.Errorf("expected stack to be empty") + } + + stack.Push(1) + if stack.IsEmpty() { + t.Errorf("expected stack to be non-empty") + } + + stack.Pop() + if !stack.IsEmpty() { + t.Errorf("expected stack to be empty") + } +}