feat: added working rendering with plugins

This commit is contained in:
David Allen 2025-08-26 22:11:17 -06:00
parent fbdaf218eb
commit 1ebea8cb73
Signed by: towk
GPG key ID: 0430CDBE22619155
7 changed files with 139 additions and 81 deletions

View file

@ -112,8 +112,12 @@ var downloadCmd = cobra.Command{
}, },
} }
var downloadProfileCmd = &cobra.Command{} var downloadProfileCmd = &cobra.Command{
var downloadPluginCmd = &cobra.Command{} Use: "profile",
}
var downloadPluginCmd = &cobra.Command{
Use: "plugin",
}
func init() { func init() {
downloadCmd.Flags().String("host", "http://localhost:5050", "Set the makeshift remote host (can be set with MAKESHIFT_HOST)") downloadCmd.Flags().String("host", "http://localhost:5050", "Set the makeshift remote host (can be set with MAKESHIFT_HOST)")
@ -122,5 +126,7 @@ func init() {
downloadCmd.Flags().StringSlice("profiles", []string{}, "Set the profile to use to populate data store") downloadCmd.Flags().StringSlice("profiles", []string{}, "Set the profile to use to populate data store")
downloadCmd.Flags().StringSlice("plugins", []string{}, "Set the plugins to run before downloading files") downloadCmd.Flags().StringSlice("plugins", []string{}, "Set the plugins to run before downloading files")
downloadCmd.AddCommand(downloadProfileCmd, downloadPluginCmd)
rootCmd.AddCommand(&downloadCmd) rootCmd.AddCommand(&downloadCmd)
} }

View file

@ -2,6 +2,7 @@ package makeshift
import "git.towk2.me/towk/makeshift/pkg/storage" import "git.towk2.me/towk/makeshift/pkg/storage"
type ProfileMap map[string]*Profile
type Profile struct { type Profile struct {
ID string `json:"id"` // profile ID ID string `json:"id"` // profile ID
Description string `json:"description,omitempty"` // profile description Description string `json:"description,omitempty"` // profile description

View file

@ -5,7 +5,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
configurator "git.towk2.me/towk/makeshift/pkg" makeshift "git.towk2.me/towk/makeshift/pkg"
"git.towk2.me/towk/makeshift/pkg/storage" "git.towk2.me/towk/makeshift/pkg/storage"
"github.com/nikolalohinski/gonja/v2" "github.com/nikolalohinski/gonja/v2"
"github.com/nikolalohinski/gonja/v2/exec" "github.com/nikolalohinski/gonja/v2/exec"
@ -17,13 +17,15 @@ type Jinja2 struct{}
func (p *Jinja2) Name() string { return "jinja2" } func (p *Jinja2) Name() string { return "jinja2" }
func (p *Jinja2) Version() string { return "v0.0.1-alpha" } func (p *Jinja2) Version() string { return "v0.0.1-alpha" }
func (p *Jinja2) Description() string { return "Renders Jinja 2 templates" } func (p *Jinja2) Description() string { return "Renders Jinja 2 templates" }
func (p *Jinja2) Metadata() configurator.Metadata { func (p *Jinja2) Metadata() makeshift.Metadata {
return configurator.Metadata{ return makeshift.Metadata{
"author.name": "David J. Allen", "author": map[string]any{
"author.email": "davidallendj@gmail.com", "name": "David J. Allen",
"author.links": []string{ "email": "davidallendj@gmail.com",
"https://github.com/davidallendj", "links": []string{
"https://git.towk2.me/towk", "https://github.com/davidallendj",
"https://git.towk2.me/towk",
},
}, },
} }
} }
@ -34,25 +36,32 @@ func (p *Jinja2) Init() error {
return nil return nil
} }
func (p *Jinja2) Run(data storage.KVStore, args []string) error { func (p *Jinja2) Run(store storage.KVStore, args []string) error {
// render the files using Jinja 2 from args // render the files using Jinja 2 from args
var ( var (
rendered []string mappings struct {
Data map[string]any `json:"data"`
}
context *exec.Context context *exec.Context
template *exec.Template template *exec.Template
mappings map[string]any profiles any // makeshift.ProfileMap
input any // must be a byte array input any // []byte
output bytes.Buffer output bytes.Buffer
err error err error
) )
log.Debug(). log.Debug().
Str("plugin", p.Name()). Str("plugin", p.Name()).
Any("data", data). Any("store", store).
// Bytes("input", input.([]byte)). Strs("args", args).
Int("arg_count", len(args)). Int("arg_count", len(args)).
Msg("Run()") Msg("(jinja2) Run()")
input, err = data.Get("file") profiles, err = store.Get("profiles")
if err != nil {
return fmt.Errorf("(jinja2) failed to get profiles: %v", err)
}
input, err = store.Get("file")
if err != nil { if err != nil {
return fmt.Errorf("(jinja2) failed to get input data: %v", err) return fmt.Errorf("(jinja2) failed to get input data: %v", err)
} }
@ -63,35 +72,56 @@ func (p *Jinja2) Run(data storage.KVStore, args []string) error {
return fmt.Errorf("(jinja2) failed to get template from args: %v", err) return fmt.Errorf("(jinja2) failed to get template from args: %v", err)
} }
// get mappings from shared data // get mappings from shared data (optional)
shared, err := data.Get("shared") shared, err := store.Get("shared")
if err != nil { if err != nil {
return fmt.Errorf("(jinja2) failed to get data from store: %v", err) log.Warn().Err(err).Msg("(jinja2) could not retrieve shared data")
} else {
err = json.Unmarshal(shared.([]byte), &mappings)
if err != nil {
return fmt.Errorf("(jinja2) failed to unmarshal mappings from shared data: %v", err)
}
} }
err = json.Unmarshal(shared.([]byte), &mappings) var ps = make(map[string]any)
if err != nil { for profileID, profile := range profiles.(makeshift.ProfileMap) {
return fmt.Errorf("(jinja2) failed to unmarshal mappings from shared data: %v", err) ps[profileID] = map[string]any{
"id": profile.ID,
"description": profile.Description,
"data": profile.Data,
}
} }
data.Set("mappings", mappings) // inject profiles and plugin-specific mapping
mappings.Data = map[string]any{
"makeshift": map[string]any{
"profiles": ps,
"plugin": map[string]any{
"name": p.Name(),
"version": p.Version(),
"description": p.Description(),
"metadata": p.Metadata(),
},
},
}
log.Debug().Any("mappings", mappings).Send()
// use the provided data in the store to render templates // use the provided data in the store to render templates
// NOTE: this may be changed to specifically use "shared" data instead // NOTE: this may be changed to specifically use "shared" data instead
context = exec.NewContext(data.GetData().(map[string]any)) context = exec.NewContext(mappings.Data)
if err = template.Execute(&output, context); err != nil { // Prints: Hello Bob! if err = template.Execute(&output, context); err != nil { // Prints: Hello Bob!
return fmt.Errorf("(jinja2) failed to render template: %v", err) return fmt.Errorf("(jinja2) failed to render template: %v", err)
} }
rendered = append(rendered, output.String())
// write render templates to data store output // write render templates to data store output
data.Set("out", rendered) store.Set("out", output.String())
return nil return nil
} }
func (p *Jinja2) Cleanup() error { func (p *Jinja2) Cleanup() error {
// nothing to clean up // nothing to clean up
log.Debug().Str("plugin", p.Name()).Msg("jinja2.Cleanup()") log.Debug().Str("plugin", p.Name()).Msg("(jinja2) Cleanup()")
return nil return nil
} }

View file

@ -82,21 +82,23 @@ func (p *SmdClient) Version() string { return "v0.0.1-alpha" }
func (p *SmdClient) Description() string { return "Fetchs data from SMD and writes to store" } func (p *SmdClient) Description() string { return "Fetchs data from SMD and writes to store" }
func (p *SmdClient) Metadata() makeshift.Metadata { func (p *SmdClient) Metadata() makeshift.Metadata {
return makeshift.Metadata{ return makeshift.Metadata{
"author.name": "David J. Allen", "author": map[string]any{
"author.email": "davidallendj@gmail.com", "name": "David J. Allen",
"author.links": []string{ "email": "davidallendj@gmail.com",
"https://github.com/davidallendj", "links": []string{
"https://git.towk2.me/towk", "https://github.com/davidallendj",
"https://git.towk2.me/towk",
},
}, },
} }
} }
func (p *SmdClient) Init() error { func (p *SmdClient) Init() error {
log.Debug().Str("plugin", p.Name()).Msg("smd.Init()") log.Debug().Str("plugin", p.Name()).Msg("(smd) Init()")
return nil return nil
} }
func (p *SmdClient) Run(data storage.KVStore, args []string) error { func (p *SmdClient) Run(store storage.KVStore, args []string) error {
// set all the defaults for variables // set all the defaults for variables
var ( var (
client SmdClient client SmdClient
@ -104,6 +106,13 @@ func (p *SmdClient) Run(data storage.KVStore, args []string) error {
err error err error
) )
log.Debug().
Str("plugin", p.Name()).
Strs("args", args).
Int("arg_count", len(args)).
Any("store", store).
Msg("(smd) Run()")
// if we have a client, try making the request for the ethernet interfaces // if we have a client, try making the request for the ethernet interfaces
err = client.FetchEthernetInterfaces() err = client.FetchEthernetInterfaces()
if err != nil { if err != nil {
@ -124,14 +133,14 @@ func (p *SmdClient) Run(data storage.KVStore, args []string) error {
if err != nil { if err != nil {
return fmt.Errorf("(smd) failed to marshal SMD client: %v") return fmt.Errorf("(smd) failed to marshal SMD client: %v")
} }
data.Set("shared", bytes) store.Set("shared", bytes)
// apply template substitutions and return output as byte array // apply template substitutions and return output as byte array
return nil return nil
} }
func (p *SmdClient) Cleanup() error { func (p *SmdClient) Cleanup() error {
log.Debug().Str("plugin", p.Name()).Msg("smd.Init()") log.Debug().Str("plugin", p.Name()).Msg("(smd) Init()")
return nil return nil
} }

View file

@ -20,10 +20,20 @@ const (
<html> <html>
<body> <body>
<p> <p>
Plugin Information:
Name: {{ makeshift.plugin.name }}
Version: {{ makeshift.plugin.version }}
Description: {{ makeshift.plugin.description }}
Metadata: {{ makeshift.plugin.metadata }}
Profile Information:
ID: {{ makeshift.profiles.default.id }}
Description: {{ makeshift.profiles.default.description }}
# setup environment variables</br> # setup environment variables</br>
export MAKESHIFT_HOST={{ makeshift.host }}</br> export MAKESHIFT_HOST={{ makeshift.profiles.default.data.host }}</br>
export MAKESHIFT_PATH={{ makeshift.path }}</br> export MAKESHIFT_PATH={{ makeshift.profiles.default.data.path }}</br>
export MAKESHIFT_SERVER_ROOT={{ makeshift.server.root }}</br> export MAKESHIFT_SERVER_ROOT={{ makeshift.profiles.default.data.server_root }}</br>
</br> </br>
# start the service</br> # start the service</br>
makeshift serve --root $HOME/apps/makeshift/server --init</br> makeshift serve --root $HOME/apps/makeshift/server --init</br>
@ -53,13 +63,9 @@ const (
"id": "default", "id": "default",
"description": "Makeshift default profile", "description": "Makeshift default profile",
"data": { "data": {
"makeshift": { "host": "localhost",
"host": "localhost", "path": "/test",
"path": "/test", "server_root": "./test"
"server": {
"root": "/test"
}
}
} }
} }
` `

View file

@ -20,8 +20,8 @@ func (s *Service) Download() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var ( var (
path = s.PathForData() + strings.TrimPrefix(r.URL.Path, "/download") path = s.PathForData() + strings.TrimPrefix(r.URL.Path, "/download")
pluginNames = r.URL.Query()["plugins"] pluginNames = strings.Split(r.URL.Query().Get("plugins"), ",")
profileIDs = r.URL.Query()["profiles"] profileIDs = strings.Split(r.URL.Query().Get("profiles"), ",")
fileInfo os.FileInfo fileInfo os.FileInfo
out *os.File out *os.File
@ -126,38 +126,42 @@ func (s *Service) Download() http.HandlerFunc {
log.Error().Errs("errs", errs).Msg("errors occurred loading plugins") log.Error().Errs("errs", errs).Msg("errors occurred loading plugins")
errs = []error{} errs = []error{}
} }
if len(hooks) > 0 {
// run pre-hooks to modify the contents of the file before archiving // run pre-hooks to modify the contents of the file before archiving
log.Debug().Int("hook_count", len(hooks)).Msg("running hooks") log.Debug().Int("hook_count", len(hooks)).Msg("running hooks")
for _, hook := range hooks { for _, hook := range hooks {
log.Debug().Any("hook", map[string]any{ log.Debug().Any("hook", map[string]any{
"data": hook.Data, "store": hook.Data,
"args": hook.Args, "args": hook.Args,
"plugin": map[string]string{ "plugin": map[string]string{
"name": hook.Plugin.Name(), "name": hook.Plugin.Name(),
"description": hook.Plugin.Description(), "description": hook.Plugin.Description(),
"version": hook.Plugin.Version(), "version": hook.Plugin.Version(),
}, },
}).Send() }).Send()
err = hook.Run() err = hook.Run()
if err != nil { if err != nil {
log.Error().Err(err).Str("plugin", hook.Plugin.Name()).Msg("failed to run plugin") log.Error().Err(err).Str("plugin", hook.Plugin.Name()).Msg("failed to run plugin")
continue 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
}
w.Write([]byte(data.(string)))
} else {
w.Write(contents)
} }
// 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
}
w.Write([]byte(data.(string)))
} }
} else { } else {
s.writeErrorResponse(w, err.Error(), http.StatusBadRequest) s.writeErrorResponse(w, err.Error(), http.StatusBadRequest)
@ -228,6 +232,7 @@ func (s *Service) GetStatus(w http.ResponseWriter, r *http.Request) {
func (s *Service) loadProfiles(profileIDs []string, store storage.KVStore, errs []error) []error { func (s *Service) loadProfiles(profileIDs []string, store storage.KVStore, errs []error) []error {
// load data from profiles into the data store // load data from profiles into the data store
var profiles = make(makeshift.ProfileMap, len(profileIDs))
for i, profileID := range profileIDs { for i, profileID := range profileIDs {
var ( var (
profilePath = s.PathForProfileWithID(profileID) profilePath = s.PathForProfileWithID(profileID)
@ -251,8 +256,9 @@ func (s *Service) loadProfiles(profileIDs []string, store storage.KVStore, errs
errs = append(errs, err) errs = append(errs, err)
continue continue
} }
store.Set(profileID, profile) profiles[profileID] = profile
} }
store.Set("profiles", profiles)
return errs return errs
} }

View file

@ -3,7 +3,7 @@ package storage
import "fmt" import "fmt"
type MemoryStorage struct { type MemoryStorage struct {
Data map[string]any Data map[string]any `json:"data"`
} }
func (ms *MemoryStorage) Init() error { func (ms *MemoryStorage) Init() error {
@ -20,7 +20,7 @@ func (ms *MemoryStorage) Get(k string) (any, error) {
if ok { if ok {
return v, nil return v, nil
} }
return nil, fmt.Errorf("value does not exist") return nil, fmt.Errorf("value '%s' does not exist", k)
} }
func (ms *MemoryStorage) Set(k string, v any) error { func (ms *MemoryStorage) Set(k string, v any) error {