322 lines
8.5 KiB
Go
322 lines
8.5 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/fs"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"git.towk2.me/towk/makeshift/internal/archive"
|
|
makeshift "git.towk2.me/towk/makeshift/pkg"
|
|
"git.towk2.me/towk/makeshift/pkg/storage"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
func (s *Service) Download() http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
path = s.PathForData() + strings.TrimPrefix(r.URL.Path, "/download")
|
|
pluginNames = strings.Split(r.URL.Query().Get("plugins"), ",")
|
|
profileIDs = strings.Split(r.URL.Query().Get("profiles"), ",")
|
|
|
|
fileInfo os.FileInfo
|
|
out *os.File
|
|
store *storage.MemoryStorage = new(storage.MemoryStorage)
|
|
hooks []makeshift.Hook
|
|
contents []byte
|
|
errs []error
|
|
err error
|
|
)
|
|
|
|
// initialize storage
|
|
store.Init()
|
|
|
|
log.Debug().
|
|
Str("path", path).
|
|
Str("client_host", r.Host).
|
|
Strs("plugins", pluginNames).
|
|
Strs("profiles", profileIDs).
|
|
Any("query", r.URL.Query()).
|
|
Msg("Service.Download()")
|
|
|
|
// prepare profiles
|
|
errs = s.loadProfiles(profileIDs, store, errs)
|
|
if len(errs) > 0 {
|
|
log.Error().Errs("errs", errs).Msg("errors occurred loading profiles")
|
|
errs = []error{}
|
|
}
|
|
|
|
// determine if path is directory, file, or exists
|
|
if fileInfo, err = os.Stat(path); err == nil {
|
|
if fileInfo.IsDir() {
|
|
// get the final archive path
|
|
archivePath := fmt.Sprintf("%s.tar.gz", path)
|
|
|
|
log.Debug().
|
|
Str("archive_path", archivePath).
|
|
Str("type", "directory").
|
|
Msg("Service.Download()")
|
|
|
|
out, err = os.Create(archivePath)
|
|
if err != nil {
|
|
s.writeErrorResponse(w, fmt.Sprintf("failed to create named file: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// get a list of filenames to archive
|
|
filenamesToArchive := []string{}
|
|
filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
|
|
if !d.IsDir() {
|
|
filenamesToArchive = append(filenamesToArchive, path)
|
|
}
|
|
return nil
|
|
})
|
|
log.Debug().Strs("files", filenamesToArchive).Send()
|
|
|
|
// prepare plugins
|
|
hooks, errs = s.loadPlugins(pluginNames, store, nil, errs)
|
|
if len(errs) > 0 {
|
|
log.Error().Errs("errs", errs).Msg("errors occurred loading plugins")
|
|
errs = []error{}
|
|
}
|
|
|
|
// create an archive of the directory, run hooks, and download
|
|
err = archive.Create(filenamesToArchive, out, hooks)
|
|
if err != nil {
|
|
s.writeErrorResponse(w, fmt.Sprintf("failed to create archive: %v", err.Error()), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// load the final archive
|
|
contents, err = os.ReadFile(archivePath)
|
|
if err != nil {
|
|
s.writeErrorResponse(w, fmt.Sprintf("failed to read archive contents: %v", err.Error()), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// send the archive back as response
|
|
w.Header().Add("FILETYPE", "archive")
|
|
w.Write(contents)
|
|
|
|
// clean up the temporary archive
|
|
err = os.Remove(archivePath)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to remove temporary archive")
|
|
return
|
|
}
|
|
|
|
} else {
|
|
// download individual file
|
|
log.Debug().
|
|
Str("type", "file").
|
|
Msg("Service.Download()")
|
|
|
|
contents, err = os.ReadFile(path)
|
|
if err != nil {
|
|
s.writeErrorResponse(w, fmt.Sprintf("failed to read file to download: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// prepare plugins
|
|
|
|
store.Set("file", contents)
|
|
hooks, errs = s.loadPlugins(pluginNames, store, nil, errs)
|
|
if len(errs) > 0 {
|
|
log.Error().Errs("errs", errs).Msg("errors occurred loading plugins")
|
|
errs = []error{}
|
|
}
|
|
if len(hooks) > 0 {
|
|
// run pre-hooks to modify the contents of the file before archiving
|
|
log.Debug().Int("hook_count", len(hooks)).Msg("running hooks")
|
|
for _, hook := range hooks {
|
|
log.Debug().Any("hook", map[string]any{
|
|
"store": hook.Data,
|
|
"args": hook.Args,
|
|
"plugin": map[string]string{
|
|
"name": hook.Plugin.Name(),
|
|
"description": hook.Plugin.Description(),
|
|
"version": hook.Plugin.Version(),
|
|
},
|
|
}).Send()
|
|
err = hook.Run()
|
|
if err != nil {
|
|
log.Error().
|
|
Err(err).
|
|
Str("plugin", hook.Plugin.Name()).
|
|
Msg("failed to run plugin")
|
|
continue
|
|
}
|
|
}
|
|
|
|
// take the contents from the last hook and update files
|
|
var (
|
|
hook = hooks[len(hooks)-1]
|
|
data any
|
|
)
|
|
data, err = hook.Data.Get("out")
|
|
if err != nil {
|
|
s.writeErrorResponse(w, fmt.Sprintf("failed to get data from hook: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// send processed (with plugins) file back as response
|
|
w.Header().Add("FILETYPE", "file")
|
|
w.Write(data.([]byte))
|
|
} else {
|
|
// send non-processed file back as response
|
|
w.Header().Add("FILETYPE", "file")
|
|
w.Write(contents)
|
|
}
|
|
|
|
}
|
|
} else {
|
|
s.writeErrorResponse(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Service) Upload() http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
}
|
|
}
|
|
|
|
func (s *Service) UploadPlugin() http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
}
|
|
}
|
|
|
|
func (s *Service) UploadProfile() http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
}
|
|
}
|
|
|
|
func (s *Service) List() http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
path = s.PathForData() + strings.TrimPrefix(r.URL.Path, "/list")
|
|
entries []string
|
|
body []byte
|
|
err error
|
|
)
|
|
|
|
// show what we're listing
|
|
log.Debug().Str("path", path).Msg("Service.List()")
|
|
|
|
// walk directory and show all entries "ls"
|
|
err = filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
entries = append(entries, d.Name())
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
switch err {
|
|
case fs.ErrNotExist, fs.ErrInvalid:
|
|
http.Error(w, "No such file or directory...", http.StatusBadRequest)
|
|
case fs.ErrPermission:
|
|
http.Error(w, "Invalid permissions...", http.StatusForbidden)
|
|
default:
|
|
http.Error(w, "Something went wrong (file or directory *probably* does not exist)...", http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
|
|
body, err = json.Marshal(entries)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Write(body)
|
|
}
|
|
}
|
|
|
|
func (s *Service) GetStatus(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
err := json.NewEncoder(w).Encode(map[string]any{
|
|
"code": http.StatusOK,
|
|
"message": "The makeshift server is healthy",
|
|
})
|
|
if err != nil {
|
|
fmt.Printf("failed to encode JSON response body: %v\n", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (s *Service) loadProfiles(profileIDs []string, store storage.KVStore, errs []error) []error {
|
|
// load data from profiles into the data store
|
|
var profiles = make(makeshift.ProfileMap, len(profileIDs))
|
|
for i, profileID := range profileIDs {
|
|
var (
|
|
profilePath = s.PathForProfileWithID(profileID)
|
|
profile *makeshift.Profile
|
|
err error
|
|
)
|
|
if i > DEFAULT_PROFILES_MAX_COUNT {
|
|
log.Warn().Msg("max profiles count reached...stopping")
|
|
return errs
|
|
}
|
|
if profileID == "" {
|
|
log.Warn().Msg("profile ID is empty...skipping")
|
|
continue
|
|
}
|
|
log.Debug().
|
|
Str("id", profileID).
|
|
Str("path", profilePath).
|
|
Msg("load profile")
|
|
profile, err = LoadProfileFromFile(profilePath)
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
profiles[profileID] = profile
|
|
}
|
|
store.Set("profiles", profiles)
|
|
|
|
return errs
|
|
}
|
|
|
|
func (s *Service) loadPlugins(pluginNames []string, store storage.KVStore, args []string, errs []error) ([]makeshift.Hook, []error) {
|
|
// create hooks to run from provided plugins specified
|
|
var hooks []makeshift.Hook
|
|
for i, pluginName := range pluginNames {
|
|
var (
|
|
pluginPath string = s.PathForPluginWithName(pluginName)
|
|
plugin makeshift.Plugin
|
|
err error
|
|
)
|
|
if i > DEFAULT_PLUGINS_MAX_COUNT {
|
|
log.Warn().Msg("max plugins count reached or exceeded...stopping")
|
|
return hooks, errs
|
|
}
|
|
if pluginName == "" {
|
|
log.Warn().Msgf("no plugin name found with index %d...skipping", i)
|
|
continue
|
|
}
|
|
log.Debug().
|
|
Str("name", pluginName).
|
|
Str("path", pluginPath).
|
|
Msg("load plugin")
|
|
|
|
// load the plugin from disk
|
|
plugin, err = LoadPluginFromFile(pluginPath)
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
hooks = append(hooks, makeshift.Hook{
|
|
Data: store,
|
|
Args: args,
|
|
Plugin: plugin,
|
|
})
|
|
}
|
|
return hooks, errs
|
|
}
|