Skip to content

Commit

Permalink
Add a custom assets server instead of using nginx.
Browse files Browse the repository at this point in the history
  • Loading branch information
GrahamDumpleton committed Jul 30, 2023
1 parent 22305b4 commit a99f4cb
Show file tree
Hide file tree
Showing 10 changed files with 360 additions and 44 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build-and-publish-images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
- image: secrets-manager
- image: tunnel-manager
- image: image-cache
- image: assets-server

steps:
- name: Check out the repository
Expand Down
16 changes: 12 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,23 @@ build-all-images: build-session-manager build-training-portal \
build-base-environment build-jdk8-environment build-jdk11-environment \
build-jdk17-environment build-conda-environment build-docker-registry \
build-pause-container build-secrets-manager build-tunnel-manager \
build-image-cache
build-image-cache build-assets-server

push-all-images: push-session-manager push-training-portal \
push-base-environment push-jdk8-environment push-jdk11-environment \
push-jdk17-environment push-conda-environment push-docker-registry \
push-pause-container push-secrets-manager push-tunnel-manager \
push-image-cache
push-image-cache push-assets-server

build-core-images: build-session-manager build-training-portal \
build-base-environment build-docker-registry build-pause-container \
build-secrets-manager build-tunnel-manager build-image-cache
build-secrets-manager build-tunnel-manager build-image-cache \
build-assets-server

push-core-images: push-session-manager push-training-portal \
push-base-environment push-docker-registry push-pause-container \
push-secrets-manager push-tunnel-manager push-image-cache
push-secrets-manager push-tunnel-manager push-image-cache \
push-assets-server

build-session-manager:
docker build --platform $(DOCKER_PLATFORM) -t $(IMAGE_REPOSITORY)/educates-session-manager:$(PACKAGE_VERSION) session-manager
Expand Down Expand Up @@ -113,6 +115,12 @@ build-image-cache:
push-image-cache: build-image-cache
docker push $(IMAGE_REPOSITORY)/educates-image-cache:$(PACKAGE_VERSION)

build-assets-server:
docker build --platform $(DOCKER_PLATFORM) -t $(IMAGE_REPOSITORY)/educates-assets-server:$(PACKAGE_VERSION) assets-server

push-assets-server: build-assets-server
docker push $(IMAGE_REPOSITORY)/educates-assets-server:$(PACKAGE_VERSION)

verify-cluster-essentials-config:
ifneq ("$(wildcard testing/educates-cluster-essentials-values.yaml)","")
@ytt --file carvel-packages/cluster-essentials/bundle/config --data-values-file testing/educates-cluster-essentials-values.yaml
Expand Down
28 changes: 28 additions & 0 deletions assets-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
FROM golang:1.19-buster as builder-image

WORKDIR /app

COPY . /app/

RUN go mod download && \
go build -o assets-server main.go

FROM fedora:36

RUN useradd -u 1001 -g 0 -M -d /opt/app-root/src default && \
mkdir -p /opt/app-root/src && \
chown -R 1001:0 /opt/app-root

WORKDIR /opt/app-root

COPY --from=builder-image /app/assets-server /opt/app-root/bin/

USER 1001

EXPOSE 8080

VOLUME ["/opt/app-root/data"]

ENTRYPOINT ["/opt/app-root/bin/assets-server"]

CMD ["--dir", "/opt/app-root/data", "--host", "0.0.0.0"]
9 changes: 9 additions & 0 deletions assets-server/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/vmware-tanzu-labs/educates-training-platform/assets-server

go 1.20

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
)
10 changes: 10 additions & 0 deletions assets-server/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
264 changes: 264 additions & 0 deletions assets-server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
/*
* This is a Golang application that serves static files from a specified
* directory and can also create and serve tar and zip archives of directories
* from the same directory.
*
* The application uses the cobra package for command-line argument handling. It
* allows the user to specify the directory path from which static files are
* served, the port the server listens on, and the host interface the listener
* socket is bound to.
*
* The server can handle the following types of requests:
* - Requests for regular static files (e.g., http://localhost:8080/file.txt)
* - Requests for tar archives of directories (e.g., http://localhost:8080/subdir/.tar)
* - Requests for tar.gz or .tgz archives of directories (e.g., http://localhost:8080/subdir/.tar.gz)
* - Requests for zip archives of directories (e.g., http://localhost:8080/subdir/.zip)
*/

package main

import (
"archive/tar"
"archive/zip"
"compress/gzip"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"
)

func createTarArchive(dirPath string, writer io.Writer, compress bool) error {
var tarWriter *tar.Writer
if compress {
gzipWriter := gzip.NewWriter(writer)
defer gzipWriter.Close()
tarWriter = tar.NewWriter(gzipWriter)
} else {
tarWriter = tar.NewWriter(writer)
}
defer tarWriter.Close()

return filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

relPath, err := filepath.Rel(dirPath, path)
if err != nil {
return err
}

// Create a new tar header
header, err := tar.FileInfoHeader(info, "")
if err != nil {
return err
}
header.Name = filepath.ToSlash(relPath)

// Write the header to the tar archive
if err := tarWriter.WriteHeader(header); err != nil {
return err
}

// If the file is not a directory, write its content to the tar archive
if !info.IsDir() {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()

_, err = io.Copy(tarWriter, file)
if err != nil {
return err
}
}

return nil
})
}

func createZipArchive(dirPath string, writer io.Writer) error {
zipWriter := zip.NewWriter(writer)
defer zipWriter.Close()

return filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

relPath, err := filepath.Rel(dirPath, path)
if err != nil {
return err
}

// Create a new zip header
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = filepath.ToSlash(relPath)

// Write the header to the zip archive
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}

// If the file is not a directory, write its content to the zip archive
if !info.IsDir() {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()

_, err = io.Copy(writer, file)
if err != nil {
return err
}
}

return nil
})
}

func main() {
var rootCmd = &cobra.Command{
Use: "static-server",
Short: "Serve static files from a directory",
Run: startServer,
}

var dataDir string
var port string
var host string

rootCmd.Flags().StringVarP(&dataDir, "dir", "d", "data", "Directory path containing static files")
rootCmd.Flags().StringVarP(&port, "port", "p", "8080", "Port number to listen on")
rootCmd.Flags().StringVarP(&host, "host", "H", "localhost", "Host interface to bind the listener socket")

if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

func startServer(cmd *cobra.Command, args []string) {
dataDir, _ := cmd.Flags().GetString("dir")
port, _ := cmd.Flags().GetString("port")
host, _ := cmd.Flags().GetString("host")

// Check if the data directory exists
_, err := os.Stat(dataDir)
if err != nil {
if os.IsNotExist(err) {
fmt.Println("Directory", dataDir, "does not exist. Please create the directory and put your static files in it.")
return
}
fmt.Println("Error:", err)
return
}

// Middleware for logging HTTP requests
loggingMiddleware := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Incoming request: %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}

// Create a file server handler to serve static files from the data directory
fileServer := http.FileServer(http.Dir(dataDir))

// Handle requests for tar, tar.gz, or zip archives of directories
http.Handle("/", loggingMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestedPath := r.URL.Path

// Check if the requested path ends with ".tar", ".tar.gz" or ".tgz"
if strings.HasSuffix(requestedPath, "/.tar") {
// Remove the ".tar" suffix from the path
requestedPath = strings.TrimSuffix(requestedPath, ".tar")

// Check if the path maps to a directory
fileInfo, err := os.Stat(filepath.Join(dataDir, requestedPath))
if err != nil || !fileInfo.IsDir() {
// Serve static files as the path does not map to a directory
fileServer.ServeHTTP(w, r)
return
}

// Serve the tar archive for the requested directory
w.Header().Set("Content-Type", "application/x-tar")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.tar\"", requestedPath))

err = createTarArchive(filepath.Join(dataDir, requestedPath), w, false)
if err != nil {
http.Error(w, "Error creating tar archive", http.StatusInternalServerError)
return
}
return
} else if strings.HasSuffix(requestedPath, "/.tar.gz") || strings.HasSuffix(requestedPath, "/.tgz") {
// Remove the ".tar.gz" or ".tgz" suffix from the path
requestedPath = strings.TrimSuffix(requestedPath, ".tar.gz")
requestedPath = strings.TrimSuffix(requestedPath, ".tgz")

// Check if the path maps to a directory
fileInfo, err := os.Stat(filepath.Join(dataDir, requestedPath))
if err != nil || !fileInfo.IsDir() {
// Serve static files as the path does not map to a directory
fileServer.ServeHTTP(w, r)
return
}

// Serve the tar.gz archive for the requested directory
w.Header().Set("Content-Type", "application/gzip")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.tar.gz\"", requestedPath))

err = createTarArchive(filepath.Join(dataDir, requestedPath), w, true)
if err != nil {
http.Error(w, "Error creating tar.gz archive", http.StatusInternalServerError)
return
}
return
} else if strings.HasSuffix(requestedPath, "/.zip") {
// Remove the ".zip" suffix from the path
requestedPath = strings.TrimSuffix(requestedPath, ".zip")

// Check if the path maps to a directory
fileInfo, err := os.Stat(filepath.Join(dataDir, requestedPath))
if err != nil || !fileInfo.IsDir() {
// Serve static files as the path does not map to a directory
fileServer.ServeHTTP(w, r)
return
}

// Serve the zip archive for the requested directory
w.Header().Set("Content-Type", "application/zip")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", requestedPath))

err = createZipArchive(filepath.Join(dataDir, requestedPath), w)
if err != nil {
http.Error(w, "Error creating zip archive", http.StatusInternalServerError)
return
}
return
}

// Serve static files
fileServer.ServeHTTP(w, r)
})))

// Start the server on the specified host and port
addr := host + ":" + port
fmt.Println("Server is running on http://" + addr)
err = http.ListenAndServe(addr, nil)
if err != nil {
fmt.Println("Error:", err)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ imageVersions:
image: "rancher/k3s:v1.25.3-k3s1"
- name: loftsh-vcluster
image: "loftsh/vcluster:0.13.0"
- name: nginx-server
image: "bitnami/nginx:1.22.1"
- name: contour-bundle
#! contour.community.tanzu.vmware.com.1.22.0
image: "projects.registry.vmware.com/tce/contour@sha256:b68ad8ec3012db7d2a2e84f8544685012e2dca09d28d54dce8735fb60f0d05bf"
4 changes: 2 additions & 2 deletions carvel-packages/training-platform/config/images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ imageVersions:
image: #@ image_reference("tunnel-manager")
- name: image-cache
image: #@ image_reference("image-cache")
- name: assets-server
image: #@ image_reference("assets-server")
- name: debian-base-image
image: "debian:sid-20230502-slim"
- name: docker-in-docker
Expand All @@ -51,8 +53,6 @@ imageVersions:
image: "rancher/k3s:v1.25.3-k3s1"
- name: loftsh-vcluster
image: "loftsh/vcluster:0.13.0"
- name: nginx-server
image: "bitnami/nginx:1.22.1"
- name: contour-bundle
#! contour.community.tanzu.vmware.com.1.22.0
image: "projects.registry.vmware.com/tce/contour@sha256:b68ad8ec3012db7d2a2e84f8544685012e2dca09d28d54dce8735fb60f0d05bf"
Loading

0 comments on commit a99f4cb

Please sign in to comment.