Compare commits

..

No commits in common. "main" and "major-rewrite" have entirely different histories.

32 changed files with 185 additions and 1212 deletions

1
.gitignore vendored
View file

@ -11,4 +11,3 @@ tests/data
tests/downloads
tests/profiles
tests/plugins
logs/

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "makeshift.wiki"]
path = makeshift.wiki
url = https://git.towk2.me/towk/makeshift.wiki.git

View file

@ -1,29 +1,14 @@
FROM archlinux:latest AS builder
FROM cgr.dev/chainguard/wolfi-base
RUN pacman -Sy
RUN pacman -S go git gcc binutils bash --noconfirm
RUN apk add --no-cache tini bash
RUN mkdir -p /configurator
WORKDIR /tmp
RUN git clone https://git.towk2.me/towk/makeshift.git
# nobody 65534:65534
USER 65534:65534
WORKDIR /tmp/makeshift
# copy the binary and all of the default plugins
COPY configurator /configurator/configurator
RUN go mod tidy && \
go build && \
mkdir -p /makeshift
RUN ./makeshift init /makeshift
RUN ./makeshift plugins compile ./pkg/plugins/jinja2/jinja2.go -o ./tmp/plugins/jinja.so
CMD ["/configurator/configurator"]
FROM alpine:latest
COPY --from=builder /tmp/makeshift/makeshift /usr/local/bin
COPY --from=builder /tmp/makeshift/tmp/plugins/* /makeshift/plugins/
RUN chmod +x /usr/local/bin/makeshift
RUN mkdir -p /makeshift/logs && \
touch /makeshift/logs/makeshift.log
ENTRYPOINT ["/usr/local/bin/makeshift"]
ENTRYPOINT [ "/sbin/tini", "--" ]

30
LICENSE
View file

@ -1,15 +1,21 @@
Copyright (C) 2015 David J. Allen
MIT License
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
Copyright © 2025 David J. Allen
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
You should have received a copy of the GNU General Public License
along with this program; if not, see
<https://www.gnu.org/licenses/>.
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View file

@ -3,7 +3,7 @@
The `makeshift` tool is a service that serves files and CLI that downloads them with a couple of handy features baked-in. Although the CLI and server component function more like a glorified FTP, the power of this tool comes from the plugin system. For example, the file cobbler is built to run external plugins for more advanced processing files before serving them (e.g. fetching from a data source, rendering Jinja 2 templates, etc.).
## Build and Go!
## Building and Go!
The `makeshift` tool is built using standard `go` build tools. To get started, clone the project, download the dependencies, and build the project:
@ -38,7 +38,7 @@ Start the server. The `--init` flag with create the default files and directory
makeshift serve --root $HOME/apps/makeshift/server --init
```
From here, you might want to see what files, plugins, and profiles that are available by default.
From here, you might want to see what files are available by default.
```bash
# list the files in the root directory
@ -60,7 +60,7 @@ makeshift list profiles
makeshift list profiles default
```
Then, we can start downloading some files or directories (as archives) both with and without running plugins.
Then, we can start downloading some files or directories (as archives).
```bash
# download all data (notice --host and --port are not set here)
@ -93,24 +93,24 @@ makeshift download profile default
```bash
# upload a single file in root directory
makeshift upload -d @compute-base.yaml
makeshift upload -d @compute-base.yaml
# upload a directory (not working yet...)
makeshift upload -d @setup/
# upload a directory (not working yet...)
makeshift upload -d @setup/
# upload an archive (extracted and saved on server - not working yet...)
makeshift upload -d @setup.tar.gz -t archive
# upload an archive (extracted and saved on server - not working yet...)
makeshift upload -d @setup.tar.gz -t archive
# upload a new profile
makeshift upload profile -d @compute.json kubernetes.json
# upload a new profile
makeshift upload profile -d @compute.json kubernetes.json
# upload a new profile with a specific path
makeshift upload profile -d @kubernetes.json
makeshift upload profile -d '{"id": "custom", "data": {}}' kubernetes.json
# upload a new profile from file and directory with `-d`
makeshift upload profile -d @kubernetes.json
makeshift upload profile -d '{"id": "custom", "data": {}}' kubernetes.json
# upload a new plugin
makeshift upload plugin -d @slurm.so
makeshift upload plugin slurm.so
# upload a new plugin
makeshift upload plugin -d @slurm.so
makeshift upload plugin slurm.so
```
> [!NOTE]
@ -158,7 +158,7 @@ type Example struct{}
func (p *Example) Name() string { return "example" }
func (p *Example) Version() string { return "v0.0.1-alpha" }
func (p *Example) Description() string { return "An example plugin" }
func (p *Example) Metadata() map[string]any {
func (p *Example) Metadata() map[string]string {
return makeshift.Metadata{
"author": map[string]any{
"name": "John Smith",
@ -199,12 +199,6 @@ makeshift plugins compile src/example.go -o $MAKESHIFT_ROOT/plugins/example.so
> [!TIP]
> Make sure you move all of your plugins to `$MAKESHIFT_ROOT/plugins` to use them and should have an `*.so` name for lookup. For example, to use a custom plugin with `makeshift download -p templates/hosts.j2 --plugins my-plugin`, there has to a plugin `$MAKESHIFT_ROOT/plugins/my-plugin.so`.
### Built-in Plugins
There is a collection of built-in plugins that can be compiled using the helper script in `bin/compile-plugins.so` and saved to `$MAKESHIFT_ROOT/plugins`.
See the README for [`jinja`](pkg/plugins/jinja2/README.md) and [`smd`](pkg/plugins/smd/README.md) for more details.
## Creating Profiles
On the other hand, profiles are simply objects that contain data used to populate data stores. The `makeshift` tool does not currently use all fields of a profile which will likely be removed in the near future.
@ -239,5 +233,9 @@ Profiles can be created using JSON and only require an `id` with optional `data`
There are some features still missing that will be added later.
1. Optionally build plugins directly into the main driver
2. Protected routes that require authentication
1. Running `makeshift` locally with profiles and plugins
2. Plugin to add user data for one-time use without creating a profile
3. Optionally build plugins directly into the main driver
4. Protected routes that require authentication
5. Configuration file for persistent runs
6. `Dockerfile` and `docker-compose.yml` files to build containers

View file

@ -1,115 +0,0 @@
package cmd
import (
"os"
"git.towk2.me/towk/makeshift/pkg/util"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
const CONFIG_FILE = `---
#
# Makeshift Config
#
# Repository: https://git.towk2.me/towk/makeshift
#
# Default configuration file for 'makeshift' CLI and service.
# This file was autogenerated using 'makeshift config new' command.
#
# Set the service host.
host: http://localhost:5050
# Set the path to the file or directory to download.
path: help.txt
# Set the log file path. Logs will be written to this location.
log-file: logs/makeshift.log
# Set the log level. Possible values include 'debug', 'info', 'warn',
# 'error', 'disabled', and 'trace'.
log-level: info
# Set the plugins to use when downloading files and/or directories.
plugins:
- smd
- jinja2
# Set the positional arguments to pass to ALL plugins when downloading
# files and directories. NOTE: These arguments may be ignored or not
# used within certain plugins.
plugin-args:
# Set the key-word arguments stored in the data that is passed to ALL
# plugins when downloading files and directories. NOTE: These arguments
# may be ignored or not used within certain plugins.
plugin-kwargs:
# Set the profiles to use when downloading files and/or directories.
profiles:
- default
# Set whether to extract the archive when downloading directories.
extract: false
# Set whether to remove an archive after downloading and extracting.
# This requires that the 'extract' flag be set to 'true'.
remove-archive: false
# Set the path to a CA certificate.
cacert: ""
# Set the path to a CA key file.
keyfile: ""
`
var configCmd = &cobra.Command{
Use: "config",
Short: "Manage makeshift config file",
}
var configNewCmd = &cobra.Command{
Use: "new [path]",
Example: `
# create a new default config at specified path
makeshift config new configs/makeshift.yaml
`,
Args: cobra.ExactArgs(1),
Short: "Create a new config file",
Run: func(cmd *cobra.Command, args []string) {
for _, path := range args {
var (
overwrite, _ = cmd.Flags().GetBool("overwrite")
exists bool
err error
)
if exists, err = util.PathExists(path); err != nil {
log.Error().
Err(err).
Str("path", path).
Msg("failed to determine if path exists")
} else {
if exists && !overwrite {
log.Error().Msg("file exists and '--overwrite' flag not passed")
return
}
err := os.WriteFile(path, []byte(CONFIG_FILE), 0o644)
if err != nil {
log.Error().
Err(err).
Str("path", path).
Msg("failed to write diefault config file to path")
continue
}
}
}
},
}
func init() {
configNewCmd.Flags().BoolP("overwrite", "f", false, "Set whether to overwrite an existing file")
configCmd.AddCommand(configNewCmd)
rootCmd.AddCommand(configCmd)
}

View file

@ -1,7 +1,6 @@
package cmd
import (
"encoding/base64"
"fmt"
"net/http"
"net/url"
@ -10,13 +9,11 @@ import (
"strings"
"git.towk2.me/towk/makeshift/internal/archive"
"git.towk2.me/towk/makeshift/internal/kwargs"
"git.towk2.me/towk/makeshift/pkg/client"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
var pluginKWArgs kwargs.KWArgs = kwargs.KWArgs{}
var downloadCmd = cobra.Command{
Use: "download",
Example: `
@ -47,10 +44,8 @@ var downloadCmd = cobra.Command{
host, _ = cmd.Flags().GetString("host")
path, _ = cmd.Flags().GetString("path")
outputPath, _ = cmd.Flags().GetString("output")
configPath, _ = cmd.Flags().GetString("config")
cacertPath, _ = cmd.Flags().GetString("cacert")
pluginNames, _ = cmd.Flags().GetStringSlice("plugins")
pluginArgs, _ = cmd.Flags().GetStringSlice("plugin-args")
profileIDs, _ = cmd.Flags().GetStringSlice("profiles")
extract, _ = cmd.Flags().GetBool("extract")
removeArchive, _ = cmd.Flags().GetBool("remove-archive")
@ -62,7 +57,6 @@ var downloadCmd = cobra.Command{
err error
)
// build download query URL
query = fmt.Sprintf("/download/%s?", path)
if len(pluginNames) > 0 {
query += "plugins=" + url.QueryEscape(strings.Join(pluginNames, ","))
@ -70,23 +64,14 @@ var downloadCmd = cobra.Command{
if len(profileIDs) > 0 {
query += "&profiles=" + url.QueryEscape(strings.Join(profileIDs, ","))
}
if len(pluginArgs) > 0 {
query += "&args=" + url.QueryEscape(strings.Join(pluginArgs, ","))
}
if len(pluginKWArgs) > 0 {
query += "&kwargs=" + base64.RawURLEncoding.EncodeToString(pluginKWArgs.Bytes())
}
log.Debug().
Str("host", host).
Str("path", path).
Str("config", configPath).
Str("query", query).
Str("output", outputPath).
Strs("profiles", profileIDs).
Strs("plugins", pluginNames).
Strs("args", pluginArgs).
Any("kwargs", pluginKWArgs).
Send()
if cacertPath != "" {
@ -302,8 +287,6 @@ func init() {
downloadCmd.Flags().StringP("path", "p", ".", "Set the path to list files (can be set with MAKESHIFT_PATH)")
downloadCmd.Flags().StringSlice("profiles", []string{}, "Set the profile(s) to use to populate data store")
downloadCmd.Flags().StringSlice("plugins", []string{}, "Set the plugin(s) to run before downloading files")
downloadCmd.Flags().StringSlice("plugin-args", []string{}, "Set the argument list to pass to plugin(s)")
downloadCmd.Flags().Var(&pluginKWArgs, "plugin-kwargs", "Set the argument map to pass to plugin(s)")
downloadCmd.Flags().BoolP("extract", "x", false, "Set whether to extract archive locally after downloading")
downloadCmd.Flags().BoolP("remove-archive", "r", false, "Set whether to remove the archive after extracting (used with '--extract' flag)")
@ -311,3 +294,12 @@ func init() {
rootCmd.AddCommand(&downloadCmd)
}
// helper to write downloaded files
func writeFiles(path string, body []byte) {
var err = os.WriteFile(path, body, 0o755)
if err != nil {
log.Error().Err(err).Msg("failed to write file(s) from download")
os.Exit(1)
}
}

View file

@ -17,7 +17,7 @@ import (
var pluginsCmd = &cobra.Command{
Use: "plugins",
Short: "Manage, inspect, and compile plugins (requires Go build tools and C compiler)",
Short: "Manage, inspect, and compile plugins (requires Go build tools)",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
setenv(cmd, "host", "MAKESHIFT_HOST")
},
@ -31,13 +31,13 @@ var pluginsCompileCmd = &cobra.Command{
# try to compile all plugins in current directory
cd src/plugins
makeshift plugins compile
makeshift plugin compile
# try to compile all plugins in specified directory
makeshift plugins compile src/plugins
makeshift plugin compile src/plugins
# compile 'src/plugins/myplugin.go' and save to 'lib/myplugin.so'
makeshift plugins compile src/plugins/myplugin.go -o lib/myplugin.so
makeshift plugin compile src/plugins/myplugin.go -o lib/myplugin.so
`,
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
@ -97,7 +97,7 @@ var pluginsInspectCmd = &cobra.Command{
Args: cobra.MinimumNArgs(1),
Example: `
# inspect a plugin and print its information
makeshift plugins inspect lib/jinja2.so
makeshift plugin inspect lib/jinja2.so
`,
Run: func(cmd *cobra.Command, args []string) {
for _, path := range args {

View file

@ -4,14 +4,10 @@ import (
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
logger "git.towk2.me/towk/makeshift/pkg/log"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
var (
@ -21,28 +17,24 @@ var (
var rootCmd = cobra.Command{
Use: "makeshift",
Short: "Extensible file cobbler",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
PersistentPreRun: func(cmd *cobra.Command, args []string) {
var (
logFile, _ = cmd.Flags().GetString("log-file")
configPath, _ = cmd.Flags().GetString("config")
err error
logFile string
err error
)
// initialize the logger
logFile, _ = cmd.Flags().GetString("log-file")
err = logger.InitWithLogLevel(loglevel, logFile)
if err != nil {
log.Error().Err(err).Msg("failed to initialize logger")
os.Exit(1)
}
// You can bind cobra and viper in a few locations, but PersistencePreRunE on the root command works well
return initConfig(cmd, configPath)
},
Run: func(cmd *cobra.Command, args []string) {
// try and set flags using env vars
setenv(cmd, "log-file", "MAKESHIFT_LOG_FILE")
setenv(cmd, "log-level", "MAKESHIFT_LOG_LEVEL")
setenv(cmd, "config", "MAKESHIFT_CONFIG_FILE")
if len(args) == 0 {
err := cmd.Help()
if err != nil {
@ -56,7 +48,6 @@ var rootCmd = cobra.Command{
err := logger.LogFile.Close()
if err != nil {
log.Error().Err(err).Msg("failed to close log file")
os.Exit(1)
}
},
}
@ -74,83 +65,8 @@ func init() {
initLogger,
)
// initialize the config a single time
rootCmd.PersistentFlags().VarP(&loglevel, "log-level", "l", "Set the log level output (can be set with MAKESHIFT_LOG_LEVEL)")
rootCmd.PersistentFlags().VarP(&loglevel, "log-level", "l", "Set the log level output")
rootCmd.PersistentFlags().String("log-file", "", "Set the log file path (can be set with MAKESHIFT_LOG_FILE)")
rootCmd.PersistentFlags().StringP("config", "c", "", "Set the config file path (can be set with MAKESHIFT_CONFIG_FILE)")
}
func initLogger() {
// initialize the logger
logfile, _ := rootCmd.PersistentFlags().GetString("log-file")
err := logger.InitWithLogLevel(loglevel, logfile)
if err != nil {
log.Error().Err(err).Msg("failed to initialize logger")
os.Exit(1)
}
}
func initConfig(cmd *cobra.Command, path string) error {
// Dissect the path to separate config name from its directory
var (
isFlagSet = cmd.Flags().Changed("config")
filename = filepath.Base(path)
ext = filepath.Ext(filename)
directory = filepath.Dir(path)
v = viper.New()
)
// The 'config' flag not set, so don't continue
if !isFlagSet {
return nil
}
// Only use specified YAML file from --config or -c flag
v.SetConfigName(strings.TrimSuffix(filename, ext))
v.SetConfigType("yaml")
v.AddConfigPath(directory)
// Attempt to read the config file. Return an error if we cannot parse
// the config file or if it is not found.
if err := v.ReadInConfig(); err != nil {
if isFlagSet {
// It's okay if there isn't a config file when no path is provided
// if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
// return err
// }
switch err.(type) {
case viper.ConfigFileNotFoundError:
log.Error().
Err(err).
Str("path", path).
Msg("failed to read config")
os.Exit(1)
default:
log.Error().Err(err).Msg("failed to read config")
os.Exit(1)
}
}
}
// When we bind flags to environment variables expect that the
// environment variables are prefixed, e.g. a flag like --number
// binds to an environment variable STING_NUMBER. This helps
// avoid conflicts.
v.SetEnvPrefix("MAKESHIFT")
// Environment variables can't have dashes in them, so bind them to their equivalent
// keys with underscores, e.g. --favorite-color to STING_FAVORITE_COLOR
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
// Bind to environment variables
// Works great for simple config names, but needs help for names
// like --favorite-color which we fix in the bindFlags function
v.AutomaticEnv()
// Bind the current command's flags to viper
bindFlags(cmd, v)
return nil
}
func setenv(cmd *cobra.Command, varname string, envvar string) {
@ -173,6 +89,16 @@ func setenvp(cmd *cobra.Command, varname string, envvar string) {
}
}
func initLogger() {
// initialize the logger
logfile, _ := rootCmd.PersistentFlags().GetString("log-file")
err := logger.InitWithLogLevel(loglevel, logfile)
if err != nil {
log.Error().Err(err).Msg("failed to initialize logger")
os.Exit(1)
}
}
func handleResponseError(res *http.Response, host, query string, err error) {
if err != nil {
log.Error().Err(err).
@ -192,26 +118,3 @@ func handleResponseError(res *http.Response, host, query string, err error) {
os.Exit(1)
}
}
// helper to write downloaded files
func writeFiles(path string, body []byte) {
var err = os.WriteFile(path, body, 0o755)
if err != nil {
log.Error().Err(err).Msg("failed to write file(s) from download")
os.Exit(1)
}
}
// Bind each cobra flag to its associated viper configuration (config file and environment variable)
func bindFlags(cmd *cobra.Command, v *viper.Viper) {
cmd.Flags().VisitAll(func(f *pflag.Flag) {
// Determine the naming convention of the flags when represented in the config file
configName := f.Name
// Apply the viper config value to the flag when the flag is not set and viper has a value
if !f.Changed && v.IsSet(configName) {
val := v.Get(configName)
cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val))
}
})
}

View file

@ -1,22 +1,6 @@
package cmd
import (
"fmt"
"io/fs"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"git.towk2.me/towk/makeshift/internal/archive"
makeshift "git.towk2.me/towk/makeshift/pkg"
"git.towk2.me/towk/makeshift/pkg/service"
"git.towk2.me/towk/makeshift/pkg/storage"
"git.towk2.me/towk/makeshift/pkg/util"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
import "github.com/spf13/cobra"
var runCmd = &cobra.Command{
Use: "run",
@ -29,274 +13,16 @@ var runCmd = &cobra.Command{
export MAKESHIFT_ROOT=/opt/makeshift
# run locally similar to 'download'
makeshift run -p help.txt --plugins jinja2 --profiles default
makeshift run --plugins jinja2 --profiles default
makeshift run --root $HOME/apps/makeshift -p help.txt --plugins jinja2 --profiles default
`,
Args: cobra.NoArgs,
Short: "Run locally with plugins and profiles",
Run: func(cmd *cobra.Command, args []string) {
var (
host, _ = cmd.Flags().GetString("host")
path, _ = cmd.Flags().GetString("path")
rootPath, _ = cmd.Flags().GetString("root")
outputPath, _ = cmd.Flags().GetString("output")
cacertPath, _ = cmd.Flags().GetString("cacert")
keyfile, _ = cmd.Flags().GetString("keyfile")
timeout, _ = cmd.Flags().GetInt("timeout")
pluginNames, _ = cmd.Flags().GetStringSlice("plugins")
pluginArgs, _ = cmd.Flags().GetStringSlice("plugin-args")
profileIDs, _ = cmd.Flags().GetStringSlice("profiles")
extract, _ = cmd.Flags().GetBool("extract")
removeArchive, _ = cmd.Flags().GetBool("remove-archive")
contents []byte
parsed *url.URL
localServer *service.Service
fileInfo os.FileInfo
out *os.File
store *storage.MemoryStorage = new(storage.MemoryStorage)
hooks []makeshift.Hook
errs []error
err error
)
// parse the host to remove scheme if needed
parsed, err = url.Parse(host)
if err != nil {
log.Warn().Err(err).
Str("host", host).
Msg("could not parse host")
}
// set the server values
localServer = service.New()
localServer.Addr = parsed.Host
localServer.RootPath = rootPath
localServer.CACertFile = cacertPath
localServer.CACertKeyfile = keyfile
localServer.Timeout = time.Duration(timeout) * time.Second
// initialize storage
store.Init()
store.SetKWArgs(&pluginKWArgs)
// prepare the profiles
errs = localServer.LoadProfiles(profileIDs, store, errs)
if len(errs) > 0 {
log.Error().
Errs("errs", errs).
Msg("errors occurred loading profiles")
err = util.FormatErrors("failed to load plugins", "", errs)
errs = []error{}
}
// prepare the plugins
// 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 {
log.Error().
Err(err).
Str("path", archivePath).
Msg("failed to create named file")
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 = localServer.LoadPlugins(pluginNames, store, pluginArgs, errs)
if len(errs) > 0 {
log.Error().
Errs("errs", errs).
Strs("plugins", pluginNames).
Strs("args", pluginArgs).
Msg("errors occurred loading plugins")
errs = []error{}
return
}
// create an archive of the directory, run hooks, and download
err = archive.Create(filenamesToArchive, out, hooks)
if err != nil {
log.Error().
Err(err).
Str("path", archivePath).
Msg("failed to create archive")
return
}
// load the final archive
contents, err = os.ReadFile(archivePath)
if err != nil {
log.Error().
Err(err).
Str("path", archivePath).
Msg("failed to read archive contents")
return
}
// clean up the temporary archive
err = os.Remove(archivePath)
if err != nil {
log.Error().Err(err).Msg("failed to remove temporary archive")
return
}
// extract files if '-x' flag is passed
if extract {
var (
dir = filepath.Dir(outputPath)
base = strings.TrimSuffix(filepath.Base(outputPath), ".tar.gz")
)
err = archive.Expand(outputPath, fmt.Sprintf("%s/%s", dir, base))
if err != nil {
log.Error().Err(err).
Str("path", outputPath).
Msg("failed to expand archive")
os.Exit(1)
}
}
// optionally, remove archive if '-r' flag is passed
// NOTE: this can only be used if `-x` flag is set
if removeArchive {
if !extract {
log.Warn().Msg("requires '-x/--extract' flag to be set to 'true'")
} else {
err = os.Remove(outputPath)
if err != nil {
log.Error().Err(err).
Str("path", outputPath).
Msg("failed to remove archive")
}
}
}
} else {
// download individual file
log.Debug().
Str("type", "file").
Msg("Service.Download()")
contents, err = os.ReadFile(path)
if err != nil {
log.Error().
Err(err).
Msg("failed to read file to download")
return
}
// prepare plugins
store.Set("file", contents)
hooks, errs = localServer.LoadPlugins(pluginNames, store, pluginArgs, errs)
if len(errs) > 0 {
log.Error().
Strs("plugins", pluginNames).
Strs("args", pluginArgs).
Errs("errs", errs).
Msg("errors occurred loading plugins")
errs = []error{}
return
}
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.Init()
if err != nil {
log.Error().
Err(err).
Str("plugin", hook.Plugin.Name()).
Msg("failed to initialize plugin")
continue
}
err = hook.Run()
if err != nil {
log.Error().
Err(err).
Str("plugin", hook.Plugin.Name()).
Msg("failed to run plugin")
continue
}
err = hook.Cleanup()
if err != nil {
log.Error().
Err(err).
Str("plugin", hook.Plugin.Name()).
Msg("failed to cleanup 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 {
log.Error().
Err(err).
Str("plugin", hook.Plugin.Name()).
Msg("failed to get data from hook")
return
}
// write to file if '-o' specified otherwise stdout
if outputPath != "" {
writeFiles(outputPath, data.([]byte))
log.Debug().Str("path", outputPath).Msg("wrote file to specified path")
} else {
fmt.Println(string(data.([]byte)))
}
} else {
// write contents to file
// send non-processed file back as response
}
}
}
},
}
func init() {
runCmd.PersistentFlags().String("host", "http://localhost:5050", "Set the makeshift remote host (can be set with MAKESHIFT_HOST)")
runCmd.PersistentFlags().StringP("output", "o", "", "Set the output path to write files")
runCmd.PersistentFlags().String("cacert", "", "Set the CA certificate path to load")
runCmd.Flags().StringP("path", "p", ".", "Set the path to list files (can be set with MAKESHIFT_PATH)")
runCmd.Flags().StringSlice("profiles", []string{}, "Set the profile(s) to use to populate data store")
runCmd.Flags().StringSlice("plugins", []string{}, "Set the plugin(s) to run before downloading files")
runCmd.Flags().StringSlice("plugin-args", []string{}, "Set the argument list to pass to plugin(s)")
runCmd.Flags().Var(&pluginKWArgs, "plugin-kwargs", "Set the argument map to pass to plugin(s)")
runCmd.Flags().BoolP("extract", "x", false, "Set whether to extract archive locally after downloading")
runCmd.Flags().BoolP("remove-archive", "r", false, "Set whether to remove the archive after extracting (used with '--extract' flag)")
rootCmd.AddCommand(runCmd)
}

View file

@ -19,18 +19,17 @@ var serveCmd = &cobra.Command{
makeshift serve --root ./test --init -l debug
`,
Args: cobra.NoArgs,
// PreRun: func(cmd *cobra.Command, args []string) {
// setenv(cmd, "host", "MAKESHIFT_HOST")
// setenv(cmd, "root", "MAKESHIFT_ROOT")
// setenv(cmd, "timeout", "MAKESHIFT_TIMEOUT")
// setenv(cmd, "cacert", "MAKESHIFT_CACERT")
// setenv(cmd, "keyfile", "MAKESHIFT_KEYFILE")
// },
PreRun: func(cmd *cobra.Command, args []string) {
setenv(cmd, "host", "MAKESHIFT_HOST")
setenv(cmd, "root", "MAKESHIFT_ROOT")
setenv(cmd, "timeout", "MAKESHIFT_TIMEOUT")
setenv(cmd, "cacert", "MAKESHIFT_CACERT")
setenv(cmd, "keyfile", "MAKESHIFT_KEYFILE")
},
Run: func(cmd *cobra.Command, args []string) {
var (
host, _ = cmd.Flags().GetString("host")
rootPath, _ = cmd.Flags().GetString("root")
configPath, _ = cmd.Flags().GetString("config")
cacertPath, _ = cmd.Flags().GetString("cacert")
keyfile, _ = cmd.Flags().GetString("keyfile")
timeout, _ = cmd.Flags().GetInt("timeout")
@ -61,7 +60,6 @@ var serveCmd = &cobra.Command{
Str("host", parsed.Host).
Any("paths", map[string]string{
"root": rootPath,
"config": configPath,
"cacert": cacertPath,
"keyfile": keyfile,
"data": server.PathForData(),
@ -94,10 +92,8 @@ func init() {
serveCmd.Flags().String("host", "localhost:5050", "Set the configurator server host (can be set with MAKESHIFT_HOST)")
serveCmd.Flags().String("root", "./", "Set the root path to serve files (can be set with MAKESHIFT_ROOT)")
serveCmd.Flags().IntP("timeout", "t", 60, "Set the timeout in seconds for requests (can be set with MAKESHIFT_TIMEOUT)")
serveCmd.Flags().String("cacert", "", "Set the CA certificate path to load (can be set with MAKESHIFT_CACERT, only used if set with '--keyfile' flag)")
serveCmd.Flags().String("keyfile", "", "Set the CA key file to use (can be set with MAKESHIFT_KEYFILE, only used if set with '--cacert' flag)")
serveCmd.Flags().String("keyurl", "", "Set the JWKS remote host for JWT verification")
serveCmd.Flags().StringSlice("protect-routes", []string{}, "Set the routes to require authentication (uses default routes if not set with '--keyurl' flag)")
serveCmd.Flags().String("cacert", "", "Set the CA certificate path to load (can be set with MAKESHIFT_CACERT)")
serveCmd.Flags().String("keyfile", "", "Set the CA key file to use (can be set with MAKESHIFT_KEYFILE)")
serveCmd.MarkFlagsRequiredTogether("cacert", "keyfile")

View file

@ -1,15 +0,0 @@
services:
makeshift:
# build: ./Dockerfile
image: makeshift:latest
container_name: makeshift
network:
- internal
volumes:
- /tmp/makeshift/makeshift:/usr/local/bin
- /tmp/makeshift/plugins/*:/makeshift/server/plugins
ports:
- 5050:5050
networks:
internal:

View file

@ -1,54 +0,0 @@
---
#
# Makeshift Config
#
# Repository: https://git.towk2.me/towk/makeshift
#
# Default configuration file for 'makeshift' CLI and service.
# This file was autogenerated using 'makeshift config new' command.
#
# Set the service host.
host: http://localhost:5050
# Set the path to the file or directory to download.
path: help.txt
# Set the log file path. Logs will be written to this location.
log-file: logs/makeshift.log
# Set the log level. Possible values include 'debug', 'info', 'warn',
# 'error', 'disabled', and 'trace'.
log-level: info
# Set the plugins to use when downloading files and/or directories.
plugins:
- smd
- jinja2
# Set the positional arguments to pass to ALL plugins when downloading
# files and directories. NOTE: These arguments may be ignored or not
# used within certain plugins.
plugin-args:
# Set the key-word arguments stored in the data that is passed to ALL
# plugins when downloading files and directories. NOTE: These arguments
# may be ignored or not used within certain plugins.
plugin-kwargs:
# Set the profiles to use when downloading files and/or directories.
profiles:
- default
# Set whether to extract the archive when downloading directories.
extract: false
# Set whether to remove an archive after downloading and extracting.
# This requires that the 'extract' flag be set to 'true'.
remove-archive: false
# Set the path to a CA certificate.
cacert: ""
# Set the path to a CA key file.
keyfile: ""

38
go.mod
View file

@ -8,64 +8,40 @@ require (
github.com/OpenCHAMI/jwtauth/v5 v5.0.0-20240321222802-e6cb468a2a18
github.com/cavaliergopher/grab/v3 v3.0.1
github.com/go-chi/chi/v5 v5.1.0
github.com/kluctl/kluctl/lib v0.0.0-20250903095056-d99ecc263d5d
github.com/lestrrat-go/jwx/v2 v2.1.1
github.com/nikolalohinski/gonja/v2 v2.3.5
github.com/rs/zerolog v1.34.0
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.6
github.com/spf13/viper v1.20.1
github.com/tidwall/sjson v1.2.5
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-git/v5 v5.16.2 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jinzhu/copier v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kluctl/go-embed-python v0.0.0-3.11.11-20241219-1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.6 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tidwall/gjson v1.14.2 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.23.0 // indirect
)

101
go.sum
View file

@ -1,68 +1,37 @@
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/OpenCHAMI/jwtauth/v5 v5.0.0-20240321222802-e6cb468a2a18 h1:oBPtXp9RVm9lk5zTmDLf+Vh21yDHpulBxUqGJQjwQCk=
github.com/OpenCHAMI/jwtauth/v5 v5.0.0-20240321222802-e6cb468a2a18/go.mod h1:ggNHWgLfW/WRXcE8ZZC4S7UwHif16HVmyowOCWdNSN8=
github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4=
github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kluctl/go-embed-python v0.0.0-3.11.11-20241219-1 h1:yVXFTxa6cvhLC6TwkDEHmNaiWlDyaFEGiXd1DQdoeOU=
github.com/kluctl/go-embed-python v0.0.0-3.11.11-20241219-1/go.mod h1:3ebNU9QBrNpUO+Hj6bHaGpkh5pymDHQ+wwVPHTE4mCE=
github.com/kluctl/kluctl/lib v0.0.0-20250903095056-d99ecc263d5d h1:++9mQstGMwSb9ofOSM8FqG5WrKso6VXBqwpcAsra7vc=
github.com/kluctl/kluctl/lib v0.0.0-20250903095056-d99ecc263d5d/go.mod h1:Yf0GI0evAyhH0YpS8Rud+ROVmT30qpK4z1PCeS3BcNU=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@ -82,9 +51,8 @@ github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmt
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -96,47 +64,32 @@ github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@ -145,35 +98,29 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
@ -37,11 +36,11 @@ func (df DataFormat) Type() string {
return "DataFormat"
}
// Marshal marshals arbitrary data into a byte slice formatted as outFormat.
// MarshalData marshals arbitrary data into a byte slice formatted as outFormat.
// If a marshalling error occurs or outFormat is unknown, an error is returned.
//
// Supported values are: json, list, yaml
func Marshal(data any, outFormat DataFormat) ([]byte, error) {
func Marshal(data interface{}, outFormat DataFormat) ([]byte, error) {
switch outFormat {
case JSON:
if bytes, err := json.MarshalIndent(data, "", " "); err != nil {
@ -62,12 +61,12 @@ func Marshal(data any, outFormat DataFormat) ([]byte, error) {
}
}
// Unmarshal unmarshals a byte slice formatted as inFormat into an interface
// UnmarshalData unmarshals a byte slice formatted as inFormat into an interface
// v. If an unmarshalling error occurs or inFormat is unknown, an error is
// returned.
//
// Supported values are: json, list, yaml
func Unmarshal(data []byte, v any, inFormat DataFormat) error {
func Unmarshal(data []byte, v interface{}, inFormat DataFormat) error {
switch inFormat {
case JSON:
if err := json.Unmarshal(data, v); err != nil {
@ -93,11 +92,11 @@ func Unmarshal(data []byte, v any, inFormat DataFormat) error {
// both the standard default format (JSON) and any command line
// change to that provided by options.
func DataFormatFromFileExt(path string, defaultFmt DataFormat) DataFormat {
switch strings.TrimLeft(strings.ToLower(filepath.Ext(path)), ".") {
case JSON.String():
switch filepath.Ext(path) {
case ".json", ".JSON":
// The file is a JSON file
return JSON
case YAML.String(), "yml":
case ".yaml", ".yml", ".YAML", ".YML":
// The file is a YAML file
return YAML
}

View file

@ -1,47 +0,0 @@
package kwargs
import (
"encoding/json"
"fmt"
"git.towk2.me/towk/makeshift/internal/format"
"github.com/rs/zerolog/log"
)
const RESERVED_KEY = "kwargs"
type KWArgs map[string]any
func New() KWArgs {
return KWArgs{}
}
func (kw KWArgs) String() string {
return string(kw.Bytes())
}
func (kw KWArgs) Bytes() []byte {
b, err := json.Marshal(kw)
if err != nil {
log.Error().Err(err).Msg("failed to marshal kwargs")
return []byte("{}")
}
return b
}
func (kw *KWArgs) Set(v string /* should be JSON object*/) error {
var (
newArgs KWArgs
err error
)
err = format.Unmarshal([]byte(v), &newArgs, format.JSON)
if err != nil {
return fmt.Errorf("failed to unmarshal value for %s: %w", kw.Type(), err)
}
*kw = newArgs
return nil
}
func (kw *KWArgs) Type() string {
return "KWArgs"
}

@ -1 +0,0 @@
Subproject commit 64090892b46ffea8d1775cc37b995182aeacf31d

View file

@ -4,7 +4,6 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"slices"
"strings"
@ -75,12 +74,6 @@ func InitWithLogLevel(logLevel LogLevel, logPath string) error {
Level: level,
})
// make the log directory if necessary
err = os.MkdirAll(filepath.Dir(logPath), 0o777)
if err != nil {
return fmt.Errorf("failed to create log path directory: %v", err)
}
// add another writer to write to a log file
if logPath != "" {
LogFile, err = os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664)

View file

@ -1,34 +0,0 @@
# Makeshift Jinja 2 Plugin
This is a `makeshift` plugin that allows rendering files with Jinja 2 grammar. The plugin reads from a `storage.KVStore` passed to the `Run()` function and substitute the values in files provided with key `file`. Profiles can also be used to inject data into the store as well.
Compile the plugin if not done already.
```bash
makeshift compile pkg/plugins/jinja2/jinja2.go -o $MAKESHIFT_ROOT/plugins/jinja.so
```
Then, try download a file that contains Jinja2 variables. The `makeshift` server includes a `help.txt` and `default.json` profile to show how rendering can be done with downloads, profiles, and plugins.
First, try downloading the `help.txt` file *without* rendering.
```bash
makeshift download -p help.txt
```
Notice that there are Jinja2 variables with no substitutions done here. Now, try it again with the `jinja2` plugin and the `default` profile.
```bash
makeshift download -p help.txt --plugins jinja2 --profile default
```
You should see the same file, but now the values are populated with the content stored in the profile. Remember...the data can also be populated using other plugins as well (see the [`smd`](../smd/README.md) plugin regarding this).
Also, keep in mind that rendering happens across directories as well.
```bash
makeshift download -p templates --plugins jinja2 --profile
default
```
This will render all Jinja 2 templates before archiving and downloading the files with the client.

View file

@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"git.towk2.me/towk/makeshift/internal/kwargs"
makeshift "git.towk2.me/towk/makeshift/pkg"
"git.towk2.me/towk/makeshift/pkg/storage"
"github.com/nikolalohinski/gonja/v2"
@ -43,11 +42,10 @@ func (p *Jinja2) Run(store storage.KVStore, args []string) error {
mappings struct {
Data map[string]any `json:"data"`
}
userdata *kwargs.KWArgs
context *exec.Context
template *exec.Template
profiles any // makeshift.ProfileMap
contents any // []byte
input any // []byte
output bytes.Buffer
err error
)
@ -58,26 +56,18 @@ func (p *Jinja2) Run(store storage.KVStore, args []string) error {
Int("arg_count", len(args)).
Msg("(jinja2) Run()")
// get profile data used as variable `{{ makeshift.profiles }}`
profiles, err = store.Get("profiles")
if err != nil {
return fmt.Errorf("(jinja2) failed to get profiles: %v", err)
}
// get userdata used as variable `{{ makeshift.userdata }}`
userdata, err = store.GetKWArgs()
if err != nil {
return fmt.Errorf("(jinja2) failed to get key-word arguments: %v", err)
}
// get file contents used for templating
contents, err = store.Get("file")
input, err = store.Get("file")
if err != nil {
return fmt.Errorf("(jinja2) failed to get input data: %v", err)
}
// get the templates provided as args to the plugin
template, err = gonja.FromBytes(contents.([]byte))
template, err = gonja.FromBytes(input.([]byte))
if err != nil {
return fmt.Errorf("(jinja2) failed to get template from args: %v", err)
}
@ -93,7 +83,6 @@ func (p *Jinja2) Run(store storage.KVStore, args []string) error {
}
}
// get mappings from provided profiles `{{ makeshift.plugin.*}}`
var ps = make(map[string]any)
for profileID, profile := range profiles.(makeshift.ProfileMap) {
ps[profileID] = map[string]any{
@ -107,7 +96,6 @@ func (p *Jinja2) Run(store storage.KVStore, args []string) error {
mappings.Data = map[string]any{
"makeshift": map[string]any{
"profiles": ps,
"userdata": userdata,
"plugin": map[string]any{
"name": p.Name(),
"version": p.Version(),

View file

@ -0,0 +1,30 @@
package main
import "git.towk2.me/towk/makeshift/pkg/storage"
type Mapper struct{}
func (p *Mapper) Name() string { return "jinja2" }
func (p *Mapper) Version() string { return "test" }
func (p *Mapper) Description() string { return "Renders Jinja 2 templates" }
func (p *Mapper) Metadata() map[string]string {
return map[string]string{
"author.name": "David J. Allen",
"author.email": "davidallendj@gmail.com",
}
}
func (p *Mapper) Init() error {
// nothing to initialize
return nil
}
func (p *Mapper) Run(data storage.KVStore, args []string) error {
return nil
}
func (p *Mapper) Clean() error {
return nil
}
var Makeshift Mapper

View file

@ -1,138 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"git.towk2.me/towk/makeshift/internal/kwargs"
makeshift "git.towk2.me/towk/makeshift/pkg"
"git.towk2.me/towk/makeshift/pkg/storage"
jinja2 "github.com/kluctl/kluctl/lib/go-jinja2"
"github.com/rs/zerolog/log"
)
type Jinja2 struct{}
func (p *Jinja2) Name() string { return "pyjinja2" }
func (p *Jinja2) Version() string { return "v0.0.1-alpha" }
func (p *Jinja2) Description() string {
return "Renders Jinja 2 templates using embedded Python renderer"
}
func (p *Jinja2) Metadata() makeshift.Metadata {
return makeshift.Metadata{
"author": map[string]any{
"name": "David J. Allen",
"email": "davidallendj@gmail.com",
"links": []string{
"https://github.com/davidallendj",
"https://git.towk2.me/towk",
},
},
}
}
func (p *Jinja2) Init() error {
// nothing to initialize
log.Debug().Str("plugin", p.Name()).Msg("pyjinja2.Init()")
return nil
}
func (p *Jinja2) Run(store storage.KVStore, args []string) error {
// render the files using Jinja 2 from args
var (
mappings struct {
Data map[string]any `json:"data"`
}
userdata *kwargs.KWArgs
profiles any // makeshift.ProfileMap
input any // []byte
output string
err error
)
log.Debug().
Str("plugin", p.Name()).
Any("store", store).
Strs("args", args).
Int("arg_count", len(args)).
Msg("(pyjinja2) Run()")
// get profile data used as variable `{{ makeshift.profiles }}`
profiles, err = store.Get("profiles")
if err != nil {
return fmt.Errorf("(pyjinja2) failed to get profiles: %v", err)
}
// get userdata used as variable `{{ makeshift.userdata }}`
userdata, err = store.GetKWArgs()
if err != nil {
return fmt.Errorf("(pyjinja2) failed to get key-word arguments: %v", err)
}
// get file contents used for templating
input, err = store.Get("file")
if err != nil {
return fmt.Errorf("(pyjinja2) failed to get input data: %v", err)
}
// get mappings from shared data (optional)
shared, err := store.Get("shared")
if err != nil {
log.Warn().Err(err).Msg("(pyjinja2) could not retrieve shared data")
} else {
err = json.Unmarshal(shared.([]byte), &mappings)
if err != nil {
return fmt.Errorf("(pyjinja2) failed to unmarshal mappings from shared data: %v", err)
}
}
// get mappings from provided profiles `{{ makeshift.plugin.*}}`
var ps = make(map[string]any)
for profileID, profile := range profiles.(makeshift.ProfileMap) {
ps[profileID] = map[string]any{
"id": profile.ID,
"description": profile.Description,
"data": profile.Data,
}
}
// get mappings supplied by this plugin
mappings.Data = map[string]any{
"makeshift": map[string]any{
"profiles": ps,
"userdata": userdata,
"plugin": map[string]any{
"name": p.Name(),
"version": p.Version(),
"description": p.Description(),
"metadata": p.Metadata(),
},
},
}
j2, err := jinja2.NewJinja2("makeshift", 1,
jinja2.WithGlobals(mappings.Data),
)
if err != nil {
return fmt.Errorf("(pyjinja2) failed to create new Jinja 2 instance: %v", err)
}
defer j2.Close()
output, err = j2.RenderString(string(input.([]byte)))
if err != nil {
return fmt.Errorf("(pyjinja2) failed to render template: %v", err)
}
log.Debug().Any("mappings", mappings).Send()
// write render templates to data store output
store.Set("out", []byte(output))
return nil
}
func (p *Jinja2) Cleanup() error {
// nothing to clean up
log.Debug().Str("plugin", p.Name()).Msg("(pyjinja2) Cleanup()")
return nil
}
var Makeshift Jinja2

View file

@ -1,13 +0,0 @@
# Makeshift SMD Plugin
This `makeshift` plugin fetchs data from [SMD](https://github.com/OpenCHAMI/smd) and stores it in the `storage.KVStore` passed to the `Run()` function. This plugin can be used in conjunction with other plugins like the `jinja2` plugin to render files using the data stored.
Here's a simple example.
```bash
makeshift download -p /etc/hosts --plugins smd,jinja2 -xr
```
This example downloads all the files in the `/etc/hosts` directory and renders the Jinja 2 templates with the data fetched from SMD. Then, the `-x` and `-r` flags tells the CLI to extract and remove the archive respectively.
Since plugins are ran in the order specified with the CLI, be sure to always call the `smd` plugin before reading from the `storage.KVStore` like in the example above or else the fetched data will not exist!

View file

@ -5,7 +5,7 @@ const (
RELPATH_PROFILES = "/profiles"
RELPATH_DATA = "/data"
RELPATH_METADATA = "/.makeshift"
RELPATH_HELP = RELPATH_DATA + "/www/index.html"
RELPATH_HELP = RELPATH_DATA + "/index.html"
RELPATH_PROFILE = RELPATH_PROFILES + "/default.json"
PATH_CONFIG = "$HOME/.config/makeshift/config.yaml"
@ -21,13 +21,14 @@ const (
<body>
<p>
Plugin Information:
Name: {{ makeshift.plugin.name }}
Version: {{ makeshift.plugin.version }}
Description: {{ makeshift.plugin.description }}
Author: {{ makeshift.plugin.metadata }}
Name: {{ makeshift.plugin.name }}
Version: {{ makeshift.plugin.version }}
Description: {{ makeshift.plugin.description }}
Author: {{ makeshift.plugin.metadata.name }} ({{ makeshift.plugin.metadata.email }})
Profile Information:
ID: {{ makeshift.profiles.default.id }}
Description: {{ makeshift.profiles.default.description }}
ID: {{ makeshift.profiles.default.id }}
Description: {{ makeshift.profiles.default.description }}
# setup environment variables</br>
export MAKESHIFT_HOST={{ makeshift.profiles.default.data.host }}</br>

View file

@ -1,7 +1,6 @@
package service
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
@ -9,14 +8,11 @@ import (
"net/http"
"os"
"path/filepath"
"slices"
"strings"
"git.towk2.me/towk/makeshift/internal/archive"
"git.towk2.me/towk/makeshift/internal/kwargs"
makeshift "git.towk2.me/towk/makeshift/pkg"
"git.towk2.me/towk/makeshift/pkg/storage"
"git.towk2.me/towk/makeshift/pkg/util"
"github.com/rs/zerolog/log"
)
@ -24,64 +20,33 @@ func (s *Service) Download() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var (
path = s.PathForData() + strings.TrimPrefix(r.URL.Path, "/download")
pluginArgs = strings.Split(r.URL.Query().Get("args"), ",")
pluginNames = strings.Split(r.URL.Query().Get("plugins"), ",")
profileIDs = strings.Split(r.URL.Query().Get("profiles"), ",")
kw *kwargs.KWArgs = new(kwargs.KWArgs)
fileInfo os.FileInfo
out *os.File
store *storage.MemoryStorage = new(storage.MemoryStorage)
hooks []makeshift.Hook
contents []byte
decoded []byte
errs []error
err error
)
// parse the KWArgs from request
decoded, err = base64.RawURLEncoding.DecodeString(r.URL.Query().Get("kwargs"))
if err != nil {
s.writeErrorResponse(w, err.Error(), http.StatusBadRequest)
return
}
// initialize storage
store.Init()
log.Debug().
Str("path", path).
Str("client_host", r.Host).
Strs("plugins", pluginNames).
Strs("profiles", profileIDs).
Strs("args", pluginArgs).
Str("kwargs", string(decoded)).
Any("query", r.URL.Query()).
Msg("Service.Download()")
err = kw.Set(string(decoded))
if err != nil {
s.writeErrorResponse(w, err.Error(), http.StatusBadRequest)
return
}
// initialize storage
err = store.Init()
if err != nil {
s.writeErrorResponse(w, err.Error(), http.StatusInternalServerError)
return
}
err = store.SetKWArgs(kw)
if err != nil {
s.writeErrorResponse(w, err.Error(), http.StatusInternalServerError)
return
}
// prepare profiles
errs = s.LoadProfiles(profileIDs, store, errs)
errs = s.loadProfiles(profileIDs, store, errs)
if len(errs) > 0 {
log.Error().
Errs("errs", errs).
Msg("errors occurred loading profiles")
err = util.FormatErrors("failed to load plugins", "", errs)
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Error().Errs("errs", errs).Msg("errors occurred loading profiles")
errs = []error{}
}
@ -113,15 +78,10 @@ func (s *Service) Download() http.HandlerFunc {
log.Debug().Strs("files", filenamesToArchive).Send()
// prepare plugins
hooks, errs = s.LoadPlugins(pluginNames, store, pluginArgs, errs)
hooks, errs = s.loadPlugins(pluginNames, store, nil, errs)
if len(errs) > 0 {
log.Error().
Errs("errs", errs).
Msg("errors occurred loading plugins")
err = util.FormatErrors("failed to load plugins", "", errs)
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Error().Errs("errs", errs).Msg("errors occurred loading plugins")
errs = []error{}
return
}
// create an archive of the directory, run hooks, and download
@ -162,16 +122,12 @@ func (s *Service) Download() http.HandlerFunc {
}
// prepare plugins
store.Set("file", contents)
hooks, errs = s.LoadPlugins(pluginNames, store, pluginArgs, errs)
hooks, errs = s.loadPlugins(pluginNames, store, nil, errs)
if len(errs) > 0 {
log.Error().
Errs("errs", errs).
Msg("errors occurred loading plugins")
err = util.FormatErrors("failed to load plugins", "", errs)
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Error().Errs("errs", errs).Msg("errors occurred loading plugins")
errs = []error{}
return
}
if len(hooks) > 0 {
// run pre-hooks to modify the contents of the file before archiving
@ -349,26 +305,7 @@ func (s *Service) GetStatus(w http.ResponseWriter, r *http.Request) {
}
}
func (s *Service) LoadProfiles(profileIDs []string, store storage.KVStore, errs []error) []error {
// check for special case profile ID (e.g. '*' or 'all')
useAll := slices.ContainsFunc(profileIDs, func(profileID string) bool {
return profileID == "*" || profileID == "all"
})
if useAll {
var (
dirpath = s.PathForProfiles()
profiles []*makeshift.Profile
err error
)
profiles, err = LoadProfilesFromDir(dirpath)
if err != nil {
errs = append(errs, err)
return errs
}
store.Set("profiles", profiles)
return nil
}
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 {
@ -401,7 +338,7 @@ func (s *Service) LoadProfiles(profileIDs []string, store storage.KVStore, errs
return errs
}
func (s *Service) LoadPlugins(pluginNames []string, store storage.KVStore, args []string, errs []error) ([]makeshift.Hook, []error) {
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 {

View file

@ -57,7 +57,7 @@ func (s *Service) Init() error {
if err != nil {
return fmt.Errorf("failed to make service profile path: %v", err)
}
err = os.MkdirAll(s.PathForData()+"/www", 0o777)
err = os.MkdirAll(s.PathForData(), 0o777)
if err != nil {
return fmt.Errorf("failed to make service data path: %v", err)
}
@ -136,42 +136,6 @@ func LoadProfileFromFile(path string) (*makeshift.Profile, error) {
return loadFromJSONFile[makeshift.Profile](path)
}
func LoadProfilesFromDir(dirpath string) ([]*makeshift.Profile, error) {
var (
profiles []*makeshift.Profile
err error
)
// walk profiles directory to load all profiles
err = filepath.Walk(dirpath, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
// skip directories
if info.IsDir() {
return nil
}
// read file contents
var profile *makeshift.Profile
profile, err = LoadProfileFromFile(path)
if err != nil {
return err
}
profiles = append(profiles, profile)
fmt.Println(path, info.Size())
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to walk directory '%s': %v", dirpath, err)
}
return profiles, nil
}
// LoadPluginFromFile loads a single plugin given a single file path
func LoadPluginFromFile(path string) (makeshift.Plugin, error) {
var (

View file

@ -1,7 +1,5 @@
package storage
import "git.towk2.me/towk/makeshift/internal/kwargs"
type DiskStorage struct{}
func (ds DiskStorage) Init() error {
@ -12,17 +10,8 @@ func (ds DiskStorage) Cleanup() error {
return nil
}
func (ds *DiskStorage) SetKWArgs(kw *kwargs.KWArgs) error {
return ds.Set(kwargs.RESERVED_KEY, kw)
}
func (ds *DiskStorage) GetKWArgs() (*kwargs.KWArgs, error) {
kw, err := ds.Get(kwargs.RESERVED_KEY)
return kw.(*kwargs.KWArgs), err
}
func (ds DiskStorage) Get(k string) (any, error) {
return nil, nil
func (ds DiskStorage) Get(k string) error {
return nil
}
func (ds DiskStorage) Set(k string, v any) error {

View file

@ -1,10 +1,6 @@
package storage
import (
"fmt"
"git.towk2.me/towk/makeshift/internal/kwargs"
)
import "fmt"
type MemoryStorage struct {
Data map[string]any `json:"data"`
@ -19,19 +15,6 @@ func (ms *MemoryStorage) Cleanup() error {
return nil
}
func (ms *MemoryStorage) SetKWArgs(kw *kwargs.KWArgs) error {
ms.Data[kwargs.RESERVED_KEY] = kw
return nil
}
func (ms *MemoryStorage) GetKWArgs() (*kwargs.KWArgs, error) {
kw, err := ms.Get(kwargs.RESERVED_KEY)
if err != nil {
return nil, err
}
return kw.(*kwargs.KWArgs), err
}
func (ms *MemoryStorage) Get(k string) (any, error) {
v, ok := ms.Data[k]
if ok {
@ -41,9 +24,6 @@ func (ms *MemoryStorage) Get(k string) (any, error) {
}
func (ms *MemoryStorage) Set(k string, v any) error {
if k == kwargs.RESERVED_KEY {
return fmt.Errorf("cannot set reserved key '%s' (use SetKWArgs() instead)", k)
}
ms.Data[k] = v
return nil
}

View file

@ -1 +0,0 @@
package storage

View file

@ -1,14 +1,9 @@
package storage
import "git.towk2.me/towk/makeshift/internal/kwargs"
type KVStore interface {
Init() error
Cleanup() error
SetKWArgs(kwargs *kwargs.KWArgs) error
GetKWArgs() (*kwargs.KWArgs, error)
Get(k string) (any, error)
Set(k string, v any) error
GetData() any

View file

@ -4,7 +4,6 @@ import (
"bytes"
"cmp"
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
@ -98,12 +97,3 @@ func CopyIf[T comparable](s []T, condition func(t T) bool) []T {
}
return f
}
func FormatErrors(message string, prefix string, errs []error) error {
var errMessage = prefix + message + "\n"
for _, err := range errs {
errMessage = fmt.Sprintf("%s %v\n", prefix, err)
}
return errors.New(errMessage)
}