slask
Some checks failed
Build and Publish / BuildAndDeploy (push) Failing after 8s

This commit is contained in:
matst80
2025-05-15 19:28:34 +02:00
commit 19b7299966
8 changed files with 513 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
name: Build and Publish
run-name: ${{ gitea.actor }} is building 🚀
on: [push]
jobs:
# BuildAndDeployAmd64:
# runs-on: amd64
# steps:
# - name: Check out repository code
# uses: actions/checkout@v4
# - name: Build docker image
# run: docker build --progress=plain -t registry.knatofs.se/go-cart-actor-amd64:latest .
# - name: Push to registry
# run: docker push registry.knatofs.se/go-cart-actor-amd64:latest
# - name: Deploy to Kubernetes
# run: kubectl apply -f deployment/deployment.yaml -n cart
# - name: Rollout amd64 deployment
# run: kubectl rollout restart deployment/cart-actor-x86 -n cart
BuildAndDeploy:
runs-on: arm64
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Build docker image
run: docker build --progress=plain -t registry.knatofs.se/go-persist .
- name: Push to registry
run: docker push registry.knatofs.se/go-persist
- name: Rollout arm64 deployment
run: kubectl rollout restart deployment/persist-arm64 -n dev

17
Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
# syntax=docker/dockerfile:1
FROM golang:alpine AS build-stage
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY *.go ./
COPY pkg ./pkg
RUN CGO_ENABLED=0 GOOS=linux go build -o /go-persist
FROM gcr.io/distroless/base-debian11
WORKDIR /
COPY --from=build-stage /go-persist /go-persist
ENTRYPOINT ["/go-persist"]

View File

@@ -0,0 +1,124 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: persist
arch: arm64
name: persist-arm64
spec:
replicas: 1
selector:
matchLabels:
app: persist
arch: arm64
template:
metadata:
labels:
app: persist
arch: arm64
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- masterpi
- key: kubernetes.io/arch
operator: In
values:
- arm64
volumes:
- name: data
nfs:
path: /i-data/7a8af061/nfs/persist
server: 10.10.1.10
imagePullSecrets:
- name: regcred
containers:
- image: registry.knatofs.se/go-persist:latest
name: persist-arm64
imagePullPolicy: Always
lifecycle:
preStop:
exec:
command: ["sleep", "15"]
ports:
- containerPort: 8080
name: web
volumeMounts:
- mountPath: "/data"
name: data
resources:
limits:
memory: "768Mi"
requests:
memory: "70Mi"
cpu: "1200m"
env:
- name: TZ
value: "Europe/Stockholm"
- name: KLARNA_API_USERNAME
valueFrom:
secretKeyRef:
name: klarna-api-credentials
key: username
- name: KLARNA_API_PASSWORD
valueFrom:
secretKeyRef:
name: klarna-api-credentials
key: password
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: AMQP_URL
value: "amqp://admin:12bananer@rabbitmq:5672/"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
---
kind: Service
apiVersion: v1
metadata:
name: persist
annotations:
prometheus.io/port: "8080"
spec:
selector:
app: persist
ports:
- name: web
port: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: persist-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
# nginx.ingress.kubernetes.io/affinity: "cookie"
# nginx.ingress.kubernetes.io/session-cookie-name: "cart-affinity"
# nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
# nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
nginx.ingress.kubernetes.io/proxy-body-size: 4m
spec:
ingressClassName: nginx
tls:
- hosts:
- storage.tornberg.me
secretName: persist-tls-secret
rules:
- host: storage.tornberg.me
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: persist
port:
number: 8080

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module git.tornberg.me/go-persist
go 1.24.0

205
main.go Normal file
View File

@@ -0,0 +1,205 @@
package main
import (
"encoding/json"
"io"
"log"
"net/http"
"net/url"
"strings"
"git.tornberg.me/go-persist/pkg/storage"
)
type Folder struct {
Id string
parent *Folder
Storage storage.Storage
Children map[string]*Folder
}
func (f *Folder) GetId() string {
return f.Id
}
func (f *Folder) GetPathIds() []string {
p := f.parent
ids := make([]string, 0)
ids = append(ids, f.GetId())
for p != nil {
ids = append(ids, p.GetId())
p = p.parent
}
return ids
}
type App struct {
Root *Folder
spawnStorage func(path []string) (storage.Storage, error)
}
func (app *App) GetFolder(pth []string) (*Folder, bool) {
current := app.Root
for _, id := range pth {
if child, exists := current.Children[id]; exists {
current = child
} else {
return nil, false
}
}
return current, true
}
func (app *App) GetOrSpawn(pth []string) (*Folder, error) {
current := app.Root
level := []string{}
for _, id := range pth {
if child, exists := current.Children[id]; exists {
current = child
level = append(level, id)
} else {
level = append(level, id)
s, err := app.spawnStorage(level)
if err != nil {
return nil, err
}
child := &Folder{
Id: id,
parent: current,
Storage: s,
Children: make(map[string]*Folder),
}
current.Children[id] = child
current = child
}
}
return current, nil
}
func (app *App) AddFolder(id string, parent *Folder) (*Folder, error) {
strg, err := app.spawnStorage(parent.GetPathIds())
if err != nil {
return nil, err
}
folder := &Folder{
Id: id,
parent: parent,
Storage: strg,
Children: make(map[string]*Folder),
}
if parent != nil {
parent.Children[id] = folder
}
return folder, nil
}
func GetPathAndFileFromUrl(u *url.URL) ([]string, string) {
if u == nil {
return []string{}, ""
}
parts := strings.Split(u.Path, "/")[1:]
fileName := parts[len(parts)-1]
return parts[:len(parts)-1], fileName
}
func esimateMimeType(filename string) string {
if strings.HasSuffix(filename, ".jpg") {
return "image/jpeg"
}
if strings.HasSuffix(filename, ".png") {
return "image/png"
}
if strings.HasSuffix(filename, ".gif") {
return "image/gif"
}
if strings.HasSuffix(filename, ".bmp") {
return "image/bmp"
}
if strings.HasSuffix(filename, ".webp") {
return "image/webp"
}
if strings.HasSuffix(filename, ".json") {
return "application/json"
}
if strings.HasSuffix(filename, ".html") || strings.HasSuffix(filename, ".htm") {
return "text/html"
}
if strings.HasSuffix(filename, ".css") {
return "text/css"
}
if strings.HasSuffix(filename, ".txt") {
return "text/plain"
}
return "application/octet-stream"
}
func main() {
// Initialize the application
rootDir, err := storage.NewDiskStorage([]string{})
if err != nil {
log.Fatal(err)
}
app := &App{
Root: &Folder{
Id: "data",
Storage: rootDir,
Children: make(map[string]*Folder),
},
spawnStorage: func(path []string) (storage.Storage, error) {
if path[0] == "tmp" {
return storage.NewMemoryStorage(path)
}
return storage.NewDiskStorage(path)
},
}
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
pth, fileName := GetPathAndFileFromUrl(r.URL)
log.Printf("Request path parts: %+v, fileName:%s", pth, fileName)
folder, err := app.GetOrSpawn(pth)
if err != nil {
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
}
log.Printf("Retrieved folder: %+v, exists: %v", folder)
if fileName == "" {
content, err := folder.Storage.List("")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(content)
} else {
if r.Method == "GET" {
content, err := folder.Storage.Get(fileName)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", esimateMimeType(fileName))
w.Header().Set("Cache-Control", "public; max-age=60")
w.Header().Set("Content-Disposition", "attachment; filename=\""+fileName+"\"")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(content)
} else {
defer r.Body.Close()
data, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
}
folder.Storage.Put(fileName, data)
w.WriteHeader(http.StatusCreated)
}
}
w.WriteHeader(http.StatusOK)
})
if err := http.ListenAndServe(":8080", mux); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,78 @@
package storage
import (
"io"
"os"
"path"
)
var (
DiskStorageBasePath = []string{"data"}
)
type DiskStorage struct {
dir string
}
func NewDiskStorage(ids []string) (Storage, error) {
pth := path.Join(DiskStorageBasePath...)
idPth := path.Join(ids...)
dir := path.Join(pth, idPth)
s := &DiskStorage{dir: dir}
err := s.ensureDir()
if err != nil {
return nil, err
}
return s, nil
}
func (ds *DiskStorage) ensureDir() error {
pth := path.Dir(ds.dir + "/")
return os.MkdirAll(pth, 0777)
}
func (ds *DiskStorage) Get(key string) (io.Reader, error) {
if err := ds.ensureDir(); err != nil {
return nil, err
}
f, err := os.Open(path.Join(ds.dir, key))
if err != nil {
return nil, err
}
return f, nil
}
func (ds *DiskStorage) Put(key string, data []byte) error {
if err := ds.ensureDir(); err != nil {
return err
}
f, err := os.Open(path.Join(ds.dir, key))
if err != nil {
return err
}
_, err = f.Write(data)
if err != nil {
return err
}
return f.Close()
}
func (ds *DiskStorage) Delete(key string) error {
// Implement disk storage delete logic here
return os.Remove(path.Join(ds.dir, key))
}
func (ds *DiskStorage) List(prefix string) ([]string, error) {
if err := ds.ensureDir(); err != nil {
return nil, err
}
data, err := os.ReadDir(path.Join(ds.dir, prefix))
if err != nil {
return nil, err
}
res := make([]string, 0, len(data))
for _, file := range data {
res = append(res, file.Name())
}
return res, nil
}

View File

@@ -0,0 +1,44 @@
package storage
import (
"bytes"
"io"
"os"
)
type MemoryStorage struct {
cache map[string][]byte
}
func NewMemoryStorage(ids []string) (Storage, error) {
s := &MemoryStorage{
cache: make(map[string][]byte),
}
return s, nil
}
func (ms *MemoryStorage) Get(key string) (io.Reader, error) {
content, ok := ms.cache[key]
if !ok {
return nil, os.ErrNotExist
}
return bytes.NewReader(content), nil
}
func (ms *MemoryStorage) Put(key string, data []byte) error {
ms.cache[key] = data
return nil
}
func (ms *MemoryStorage) Delete(key string) error {
delete(ms.cache, key)
return nil
}
func (ms *MemoryStorage) List(prefix string) ([]string, error) {
res := make([]string, 0, len(ms.cache))
for file, _ := range ms.cache {
res = append(res, file)
}
return res, nil
}

12
pkg/storage/storage.go Normal file
View File

@@ -0,0 +1,12 @@
package storage
import (
"io"
)
type Storage interface {
Get(key string) (io.Reader, error)
Put(key string, data []byte) error
Delete(key string) error
List(prefix string) ([]string, error)
}