refactor: moved plugin files

This commit is contained in:
David Allen 2025-08-24 20:34:38 -06:00
parent c24bcf34d4
commit ea4819e97a
Signed by: towk
GPG key ID: 0430CDBE22619155
5 changed files with 372 additions and 61 deletions

View file

@ -1,58 +0,0 @@
package plugin
import (
"bytes"
"git.towk2.me/towk/configurator/pkg/storage"
"github.com/nikolalohinski/gonja/v2"
"github.com/nikolalohinski/gonja/v2/exec"
)
type Jinja2 struct{}
func (p *Jinja2) Name() string { return "jinja2" }
func (p *Jinja2) Version() string { return "test" }
func (p *Jinja2) Description() string { return "Renders Jinja 2 templates" }
func (p *Jinja2) Metadata() map[string]string {
return map[string]string{
"author.name": "David J. Allen",
"author.email": "davidallendj@gmail.com",
}
}
func (p *Jinja2) Init() error {
// nothing to initialize
return nil
}
func (p *Jinja2) Run(data storage.KVStore, args []string) error {
// render the files using Jinja 2 from args
newContent := []string{}
for _, arg := range args {
var (
context *exec.Context
template *exec.Template
output bytes.Buffer
err error
)
template, err = gonja.FromString(arg)
if err != nil {
panic(err)
}
context = exec.NewContext(data.GetData().(map[string]any))
if err = template.Execute(&output, context); err != nil { // Prints: Hello Bob!
panic(err)
}
newContent = append(newContent, output.String())
}
// write back to the data storage
data.Set("out", newContent)
return nil
}
func (p *Jinja2) Cleanup() error {
// nothing to clean up
return nil
}

View file

@ -0,0 +1,98 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
configurator "git.towk2.me/towk/makeshift/pkg"
"git.towk2.me/towk/makeshift/pkg/storage"
"github.com/nikolalohinski/gonja/v2"
"github.com/nikolalohinski/gonja/v2/exec"
"github.com/rs/zerolog/log"
)
type Jinja2 struct{}
func (p *Jinja2) Name() string { return "jinja2" }
func (p *Jinja2) Version() string { return "v0.0.1-alpha" }
func (p *Jinja2) Description() string { return "Renders Jinja 2 templates" }
func (p *Jinja2) Metadata() configurator.Metadata {
return configurator.Metadata{
"author.name": "David J. Allen",
"author.email": "davidallendj@gmail.com",
"author.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("jinja2.Init()")
return nil
}
func (p *Jinja2) Run(data storage.KVStore, args []string) error {
// render the files using Jinja 2 from args
var (
rendered []string
context *exec.Context
template *exec.Template
mappings map[string]any
input any // must be a byte array
output bytes.Buffer
err error
)
log.Debug().
Str("plugin", p.Name()).
Any("data", data).
// Bytes("input", input.([]byte)).
Int("arg_count", len(args)).
Msg("Run()")
input, err = data.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(input.([]byte))
if err != nil {
return fmt.Errorf("(jinja2) failed to get template from args: %v", err)
}
// get mappings from shared data
shared, err := data.Get("shared")
if err != nil {
return fmt.Errorf("(jinja2) failed to get data from store: %v", err)
}
err = json.Unmarshal(shared.([]byte), &mappings)
if err != nil {
return fmt.Errorf("(jinja2) failed to unmarshal mappings from shared data: %v", err)
}
data.Set("mappings", mappings)
// use the provided data in the store to render templates
// NOTE: this may be changed to specifically use "shared" data instead
context = exec.NewContext(data.GetData().(map[string]any))
if err = template.Execute(&output, context); err != nil { // Prints: Hello Bob!
return fmt.Errorf("(jinja2) failed to render template: %v", err)
}
rendered = append(rendered, output.String())
// write render templates to data store output
data.Set("out", rendered)
return nil
}
func (p *Jinja2) Cleanup() error {
// nothing to clean up
log.Debug().Str("plugin", p.Name()).Msg("jinja2.Cleanup()")
return nil
}
var Makeshift Jinja2

View file

@ -1 +0,0 @@
package plugin

270
pkg/plugins/smd/smd.go Normal file
View file

@ -0,0 +1,270 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
makeshift "git.towk2.me/towk/makeshift/pkg"
"git.towk2.me/towk/makeshift/pkg/storage"
"github.com/rs/zerolog/log"
)
// An struct that's meant to extend functionality of the base HTTP client by
// adding commonly made requests to SMD. The implemented functions are can be
// used in generator plugins to fetch data when it is needed to substitute
// values for the Jinja templates used.
type SmdClient struct {
http.Client `json:"-" yaml:"-"`
Host string `yaml:"host"`
Port int `yaml:"port"`
AccessToken string `yaml:"access-token"`
RedfishEndpoints []RedfishEndpoint `json:"redfish_endpoints"`
EthernetInterfaces []EthernetInterface `json:"ethernet_interfaces"`
Components []Component `json:"components"`
}
type IPAddr struct {
IpAddress string `json:"IPAddress"`
Network string `json:"Network"`
}
type EthernetInterface struct {
ID string `json:"ID"`
Description string `json:"Description"`
MacAddress string `json:"MACAddr"`
LastUpdate string `json:"LastUpdate"`
ComponentID string `json:"ComponentID"`
Type string `json:"Type"`
IpAddresses []IPAddr `json:"IPAddresses"`
}
type Component struct {
ID string `json:"ID"`
Type string `json:"Type"`
State string `json:"State,omitempty"`
Flag string `json:"Flag,omitempty"`
Enabled *bool `json:"Enabled,omitempty"`
SwStatus string `json:"SoftwareStatus,omitempty"`
Role string `json:"Role,omitempty"`
SubRole string `json:"SubRole,omitempty"`
NID json.Number `json:"NID,omitempty"`
Subtype string `json:"Subtype,omitempty"`
NetType string `json:"NetType,omitempty"`
Arch string `json:"Arch,omitempty"`
Class string `json:"Class,omitempty"`
ReservationDisabled bool `json:"ReservationDisabled,omitempty"`
Locked bool `json:"Locked,omitempty"`
}
type RedfishEndpoint struct {
ID string `json:"ID"`
Type string `json:"Type"`
Name string `json:"Name,omitempty"` // user supplied descriptive name
Hostname string `json:"Hostname"`
Domain string `json:"Domain"`
FQDN string `json:"FQDN"`
Enabled bool `json:"Enabled"`
UUID string `json:"UUID,omitempty"`
User string `json:"User"`
Password string `json:"Password"` // Temporary until more secure method
UseSSDP bool `json:"UseSSDP,omitempty"`
MACRequired bool `json:"MACRequired,omitempty"`
MACAddr string `json:"MACAddr,omitempty"`
IPAddr string `json:"IPAddress,omitempty"`
}
func (p *SmdClient) Name() string { return "smd" }
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) Metadata() makeshift.Metadata {
return makeshift.Metadata{
"author.name": "David J. Allen",
"author.email": "davidallendj@gmail.com",
"author.links": []string{
"https://github.com/davidallendj",
"https://git.towk2.me/towk",
},
}
}
func (p *SmdClient) Init() error {
log.Debug().Str("plugin", p.Name()).Msg("smd.Init()")
return nil
}
func (p *SmdClient) Run(data storage.KVStore, args []string) error {
// set all the defaults for variables
var (
client SmdClient
bytes []byte
err error
)
// if we have a client, try making the request for the ethernet interfaces
err = client.FetchEthernetInterfaces()
if err != nil {
return fmt.Errorf("(smd) failed to fetch ethernet interfaces with client: %v", err)
}
err = client.FetchRedfishEndpoints()
if err != nil {
return fmt.Errorf("(smd) failed to fetch redfish endpoints with client: %v", err)
}
err = client.FetchComponents()
if err != nil {
return fmt.Errorf("(smd) failed to fetch components with client: %v", err)
}
// write data back to shared data store to be used by other plugins
bytes, err = json.Marshal(client)
if err != nil {
return fmt.Errorf("(smd) failed to marshal SMD client: %v")
}
data.Set("shared", bytes)
// apply template substitutions and return output as byte array
return nil
}
func (p *SmdClient) Cleanup() error {
log.Debug().Str("plugin", p.Name()).Msg("smd.Init()")
return nil
}
// Fetch the ethernet interfaces from SMD service using its API. An access token may be required if the SMD
// service SMD_JWKS_URL envirnoment variable is set.
func (client *SmdClient) FetchEthernetInterfaces() error {
var (
bytes []byte
err error
)
// make request to SMD endpoint
bytes, err = client.makeRequest("/Inventory/EthernetInterfaces")
if err != nil {
return fmt.Errorf("failed to read HTTP response: %v", err)
}
// unmarshal response body JSON and extract in object
err = json.Unmarshal(bytes, &client.EthernetInterfaces)
if err != nil {
return fmt.Errorf("failed to unmarshal response: %v", err)
}
// print what we got if verbose is set
log.Debug().Str("ethernet_interfaces", string(bytes)).Msg("found interfaces")
return nil
}
// Fetch the components from SMD using its API. An access token may be required if the SMD
// service SMD_JWKS_URL envirnoment variable is set.
func (client *SmdClient) FetchComponents() error {
var (
bytes []byte
err error
)
// make request to SMD endpoint
bytes, err = client.makeRequest("/State/Components")
if err != nil {
return fmt.Errorf("failed to make HTTP request: %v", err)
}
// make sure our response is actually JSON first
if !json.Valid(bytes) {
return fmt.Errorf("expected valid JSON response: %v", string(bytes))
}
// unmarshal response body JSON and extract in object
var tmp map[string]any
err = json.Unmarshal(bytes, &tmp)
if err != nil {
return fmt.Errorf("failed to unmarshal response: %v", err)
}
bytes, err = json.Marshal(tmp["RedfishEndpoints"].([]any))
if err != nil {
return fmt.Errorf("failed to marshal JSON: %v", err)
}
err = json.Unmarshal(bytes, &client.Components)
if err != nil {
return fmt.Errorf("failed to unmarshal response: %v", err)
}
// print what we got if verbose is set
log.Debug().Str("components", string(bytes)).Msg("found components")
return nil
}
// TODO: improve implementation of this function
func (client *SmdClient) FetchRedfishEndpoints() error {
var (
store map[string]any
rfeps []RedfishEndpoint
body []byte
err error
)
// make initial request to get JSON with 'RedfishEndpoints' as property
body, err = client.makeRequest("/Inventory/RedfishEndpoints")
if err != nil {
return fmt.Errorf("failed to make HTTP resquest: %v", err)
}
// make sure response is in JSON
if !json.Valid(body) {
return fmt.Errorf("expected valid JSON response: %s", string(body))
}
err = json.Unmarshal(body, &store)
if err != nil {
return fmt.Errorf("failed to unmarshal response: %v", err)
}
// marshal RedfishEndpoint JSON back to makeshift.RedfishEndpoint
body, err = json.Marshal(store["RedfishEndpoints"].([]any))
if err != nil {
return fmt.Errorf("failed to marshal JSON: %v", err)
}
err = json.Unmarshal(body, &rfeps)
if err != nil {
return fmt.Errorf("failed to unmarshal response: %v", err)
}
// show the final result
log.Debug().Bytes("redfish_endpoints", body).Msg("found redfish endpoints")
client.RedfishEndpoints = rfeps
return nil
}
func (client *SmdClient) makeRequest(endpoint string) ([]byte, error) {
if client == nil {
return nil, fmt.Errorf("client is nil")
}
// fetch DHCP related information from SMD's endpoint:
url := fmt.Sprintf("%s/hsm/v2%s", client.Host, endpoint)
req, err := http.NewRequest(http.MethodGet, url, bytes.NewBuffer([]byte{}))
if err != nil {
return nil, fmt.Errorf("failed to create new HTTP request: %v", err)
}
// include access token in authorzation header if found
// NOTE: This shouldn't be needed for this endpoint since it's public
if client.AccessToken != "" {
req.Header.Add("Authorization", "Bearer "+client.AccessToken)
}
// make the request to SMD
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to make request: %v", err)
}
// read the contents of the response body
return io.ReadAll(res.Body)
}
var Makeshift SmdClient

View file

@ -1,6 +1,6 @@
package plugin package main
import "git.towk2.me/towk/configurator/pkg/storage" import "git.towk2.me/towk/makeshift/pkg/storage"
type UserData struct{} type UserData struct{}
@ -26,3 +26,5 @@ func (p *UserData) Run(data storage.KVStore, args []string) error {
func (p *UserData) Clean() error { func (p *UserData) Clean() error {
return nil return nil
} }
var Makeshift UserData