Files
osmedeus/internal/template/generators.go
T
j3ssie 641cf568aa docs: add security disclaimer and annotate intentional code execution points
- Add disclaimer section in README.md warning users about arbitrary code execution risks
- Annotate exec.Command calls throughout codebase with @NOTE comments explaining intentional design
- Improve step result table formatting: add column alignment, padding for status symbols, and remove unnecessary borders
2026-02-04 16:39:19 +07:00

220 lines
5.5 KiB
Go

package template
import (
"fmt"
"maps"
"math/rand"
"os"
"os/exec"
"strconv"
"strings"
"time"
"github.com/google/uuid"
)
// GeneratorFunc is a function that generates a value from arguments
type GeneratorFunc func(args ...string) (string, error)
// registerBuiltinGenerators registers all built-in generator functions
func (e *Engine) registerBuiltinGenerators() {
maps.Copy(e.generators, builtinGenerators)
}
// generateUUID generates a new UUID
func generateUUID(args ...string) (string, error) {
return uuid.New().String(), nil
}
// generateCurrentDate generates the current date with optional format
// Default format: 2006-01-02
func generateCurrentDate(args ...string) (string, error) {
format := "2006-01-02"
if len(args) > 0 && args[0] != "" {
format = args[0]
}
return time.Now().Format(format), nil
}
// generateTimestamp generates the current Unix timestamp
func generateTimestamp(args ...string) (string, error) {
return strconv.FormatInt(time.Now().Unix(), 10), nil
}
// getEnvironmentVariable gets an environment variable with optional default
func getEnvironmentVariable(args ...string) (string, error) {
if len(args) == 0 {
return "", fmt.Errorf("getEnvVar requires at least one argument")
}
key := args[0]
value := os.Getenv(key)
if value == "" && len(args) > 1 {
value = args[1]
}
return value, nil
}
// concatenateStrings concatenates multiple strings
func concatenateStrings(args ...string) (string, error) {
return strings.Join(args, ""), nil
}
// generateRandomInt generates a random integer between min and max
func generateRandomInt(args ...string) (string, error) {
min := 0
max := 100
if len(args) >= 1 {
var err error
min, err = strconv.Atoi(args[0])
if err != nil {
return "", fmt.Errorf("invalid min value: %s", args[0])
}
}
if len(args) >= 2 {
var err error
max, err = strconv.Atoi(args[1])
if err != nil {
return "", fmt.Errorf("invalid max value: %s", args[1])
}
}
if min > max {
min, max = max, min
}
value := rand.Intn(max-min+1) + min
return strconv.Itoa(value), nil
}
// generateRandomString generates a random alphanumeric string
func generateRandomString(args ...string) (string, error) {
length := 16
if len(args) > 0 {
var err error
length, err = strconv.Atoi(args[0])
if err != nil {
return "", fmt.Errorf("invalid length: %s", args[0])
}
}
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, length)
for i := range b {
b[i] = charset[rand.Intn(len(charset))]
}
return string(b), nil
}
// executeCommand executes a shell command and returns its output
func executeCommand(args ...string) (string, error) {
if len(args) == 0 {
return "", fmt.Errorf("execCmd requires a command argument")
}
// @NOTE: This is intentional - execCmd() is a template generator function for executing
// shell commands during template rendering. Input comes from trusted workflow YAML files.
cmd := exec.Command("sh", "-c", args[0])
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("command failed: %w", err)
}
return strings.TrimSpace(string(output)), nil
}
// toLower converts a string to lowercase
func toLower(args ...string) (string, error) {
if len(args) == 0 {
return "", nil
}
return strings.ToLower(args[0]), nil
}
// toUpper converts a string to uppercase
func toUpper(args ...string) (string, error) {
if len(args) == 0 {
return "", nil
}
return strings.ToUpper(args[0]), nil
}
// trimString trims whitespace from a string
func trimString(args ...string) (string, error) {
if len(args) == 0 {
return "", nil
}
return strings.TrimSpace(args[0]), nil
}
// replaceString replaces occurrences in a string
func replaceString(args ...string) (string, error) {
if len(args) < 3 {
return "", fmt.Errorf("replace requires 3 arguments: input, old, new")
}
return strings.ReplaceAll(args[0], args[1], args[2]), nil
}
// splitString splits a string by delimiter and returns nth element
func splitString(args ...string) (string, error) {
if len(args) < 2 {
return "", fmt.Errorf("split requires at least 2 arguments: input, delimiter")
}
parts := strings.Split(args[0], args[1])
index := 0
if len(args) >= 3 {
var err error
index, err = strconv.Atoi(args[2])
if err != nil {
return "", fmt.Errorf("invalid index: %s", args[2])
}
}
if index < 0 || index >= len(parts) {
return "", nil
}
return parts[index], nil
}
// joinString joins strings with a delimiter
func joinString(args ...string) (string, error) {
if len(args) < 2 {
return "", fmt.Errorf("join requires at least 2 arguments")
}
delimiter := args[0]
parts := args[1:]
return strings.Join(parts, delimiter), nil
}
// builtinGenerators is a package-level map of all built-in generator functions.
// This allows multiple engine implementations to share the same generators.
var builtinGenerators = map[string]GeneratorFunc{
GenUUID: generateUUID,
GenCurrentDate: generateCurrentDate,
GenCurrentTimestamp: generateTimestamp,
GenGetEnvVar: getEnvironmentVariable,
GenConcat: concatenateStrings,
GenRandomInt: generateRandomInt,
GenRandomString: generateRandomString,
GenExecCmd: executeCommand,
GenToLower: toLower,
GenToUpper: toUpper,
GenTrim: trimString,
GenReplace: replaceString,
GenSplit: splitString,
GenJoin: joinString,
}
func init() {
// As of Go 1.20, there is no need to seed the default random source
// The global random generator is automatically seeded
}