commit 5f0f91adcf12087289f61c3c131630932ac4bf01 Author: Alex Borisov <79996669747@ya.ru> Date: Thu Feb 1 17:47:50 2024 +0300 Simple API to access files using link diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..fda1767 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,70 @@ +name: Actions Build Docker Image +run-name: ${{ gitea.actor }} is building new image ๐Ÿš€ +on: push + +jobs: + Build-Docker-Image: + runs-on: soaska + steps: + - run: echo "๐ŸŽ‰ The job was automatically triggered by a ${{ gitea.event_name }} event." + - name: Install Node.js + run: | + apk add --no-cache nodejs + - name: Check out repository code + uses: actions/checkout@v3 + - name: List files in the repository + run: | + ls ${{ gitea.workspace }} + - run: echo "๐Ÿ This job's status is ${{ job.status }}." + - name: Install Docker + run: | + apk add --no-cache docker + - name: Start Docker service + run: | + dockerd & + sleep 5 + docker info + - name: Build Dockerfile + run: | + docker build -t $(basename ${{ github.repository }}) . + - name: Upload Docker image to soaska.ru + run: | + package_name=$(basename ${{ github.repository }}) + branch_name=$(git rev-parse --abbrev-ref HEAD) + + docker login -u ${{ secrets.username }} -p ${{ secrets.password }} soaska.ru + docker tag $package_name soaska.ru/soaska/$package_name:$branch_name + docker tag $package_name soaska.ru/soaska/$package_name:latest + docker push soaska.ru/soaska/$package_name:$branch_name + docker push soaska.ru/soaska/$package_name:latest + Build-Binary: + runs-on: soaska + steps: + - run: echo "๐ŸŽ‰ The job was automatically triggered by a ${{ gitea.event_name }} event." + - name: Install Node.js + run: | + apk add --no-cache nodejs + - name: Check out repository code + uses: actions/checkout@v3 + - name: List files in the repository + run: | + ls ${{ gitea.workspace }} + - run: echo "๐Ÿ This job's status is ${{ job.status }}." + - name: Install go + run: | + apk add --no-cache go + - name: Install go dependencies + run: | + go mod download + - name: Build Binary + run: | + go build -o $(basename ${{ gitea.repository }}) . + - name: Upload binary to soaska.ru + run: | + package_name=$(basename ${{ gitea.repository }}) + branch_name=$(git rev-parse --abbrev-ref HEAD) + current_date=$(date +%Y-%m-%d-%H-%M-%S) + + curl --user ${{ secrets.username }}:${{ secrets.password }} \ + --upload-file $package_name \ + https://soaska.ru/api/packages/${{ secrets.username }}/generic/$package_name-$branch_name/¤t_date/$package_name \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8563117 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +.vscode +readme.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e15d4c2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +# Stage 1: Build the Rod application +FROM golang AS builder + +WORKDIR /app +COPY go.mod go.sum /app/ +RUN go mod download + +COPY *.go /app/ + +## RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . + +# Stage 2: Use a slim image for the runnable container +FROM alpine as runtime +WORKDIR /root/ +COPY --from=builder /app/main . +CMD ["./main"] \ No newline at end of file diff --git a/example.env b/example.env new file mode 100644 index 0000000..661ea4b --- /dev/null +++ b/example.env @@ -0,0 +1,6 @@ +# ะŸั€ะธะผะตั€ั‹ ะฟะตั€ะตะผะตะฝะฝั‹ั… +S3_ACCESS_KEY_ID=your_access_key_id +S3_SECRET_ACCESS_KEY=your_secret_access_key +S3_BUCKET_NAME=your_bucket_name +S3_ENDPOINT_URL=your_endpoint +PORT=8080 \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..18ee557 --- /dev/null +++ b/go.mod @@ -0,0 +1,20 @@ +module soaska.ru/soaska/MinIO-Share-S3 + +go 1.21.6 + +require ( + github.com/gorilla/mux v1.8.1 + github.com/minio/minio-go v6.0.14+incompatible +) + +require github.com/stretchr/testify v1.8.4 // indirect + +require ( + github.com/go-ini/ini v1.67.0 // indirect + github.com/joho/godotenv v1.5.1 + github.com/mitchellh/go-homedir v1.1.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1a06872 --- /dev/null +++ b/go.sum @@ -0,0 +1,26 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= +github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/minio/minio-go v6.0.14+incompatible h1:fnV+GD28LeqdN6vT2XdGKW8Qe/IfjJDswNVuni6km9o= +github.com/minio/minio-go v6.0.14+incompatible/go.mod h1:7guKYtitv8dktvNUGrhzmNlA5wrAABTQXCoesZdFQO8= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/run.go b/run.go new file mode 100644 index 0000000..cb2b716 --- /dev/null +++ b/run.go @@ -0,0 +1,128 @@ +package main + +import ( + "fmt" + "io" + "log" + "net/http" + "os" + "time" + + "github.com/gorilla/mux" + "github.com/joho/godotenv" + "github.com/minio/minio-go" +) + +func main() { + // Get environment variables + accessKeyID, secretAccessKey, bucketName, endpoint, port := getEnv() + + // Initialize MinIO client + minioClient, err := minio.New(endpoint, accessKeyID, secretAccessKey, true) + if err != nil { + log.Fatal(err) + } + + // Create a new router + router := mux.NewRouter() + + // Define the file handler + router.HandleFunc("/{path:.*}", func(w http.ResponseWriter, r *http.Request) { + log.Printf("Received request: %s %s", r.Method, r.RequestURI) + vars := mux.Vars(r) + path := vars["path"] + + log.Print("Path: ", path) + + // Get the file from S3 + object, err := minioClient.GetObject(bucketName, path, minio.GetObjectOptions{}) + if err != nil { + if minio.ToErrorResponse(err).Code == "NoSuchKey" { + log.Printf("Object not found in S3: %s", err) + http.Error(w, "Object not found", http.StatusNotFound) + return + } + log.Printf("Error getting object from S3: %s", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Get the object info + info, err := object.Stat() + if err != nil { + if minio.ToErrorResponse(err).Code == "NoSuchKey" { + log.Printf("Object not found in S3: %s", err) + http.Error(w, "Object not found", http.StatusNotFound) + return + } + log.Printf("Error getting object info: %s", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Set the content type header + contentType := info.ContentType + w.Header().Set("Content-Type", contentType) + + // Copy the file to the response writer + _, err = io.Copy(w, object) + if err != nil { + log.Printf("Error copying file to response writer: %s", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }).Methods("GET") + + router.Use(loggingMiddleware) + + // Start the server + + if port == "" { + port = "8080" + } + addr := fmt.Sprintf(":%s", port) + log.Printf("Server listening on %s", addr) + log.Fatal(http.ListenAndServe(addr, router)) +} + +func getEnv() (string, string, string, string, string) { + err := godotenv.Load() + if err != nil { + log.Fatal("Error loading .env file") + } + + // Get environment variables + port := os.Getenv("PORT") + accessKeyID := os.Getenv("S3_ACCESS_KEY_ID") + secretAccessKey := os.Getenv("S3_SECRET_ACCESS_KEY") + bucketName := os.Getenv("S3_BUCKET_NAME") + endpoint := os.Getenv("S3_ENDPOINT_URL") + + return accessKeyID, secretAccessKey, bucketName, endpoint, port +} + +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + + next.ServeHTTP(w, r) + + duration := time.Since(startTime) + log.Printf("Processed request: %s %s. Duration: %v\n", r.Method, r.RequestURI, duration) + }) +} + +func GetObjectList(bucketName string, minioClient *minio.Client) ([]string, error) { + var objectList []string + + // Get the list of objects + objectCh := minioClient.ListObjects(bucketName, "", true, nil) + for object := range objectCh { + if object.Err != nil { + return nil, object.Err + } + objectList = append(objectList, object.Key) + } + + return objectList, nil +}